crontent
Integration·WordPress·beginner

AI content for WordPress

AI content for WordPress: land Crontent posts as WordPress drafts over the REST API using Application Passwords.

How-to: Integrate Crontent with WordPress. Official docs: WordPress. Shared API contract: Publishing API and SDK.

Verify the Crontent webhook, fetch the post, then create a WordPress draft (status: draft) via the REST API using Application Passwords.

What you need

  • Crontent CRONTENT_API_KEY and CRONTENT_WEBHOOK_SECRET
  • WordPress 5.6+ with REST API enabled
  • Application Password for a user with edit_posts capability (Users → Profile → Application Passwords)
  • Webhook receiver with @crontent/sdk

Register the webhook:

ts
await crontent.webhooks.register({
  projectId: process.env.CRONTENT_PROJECT_ID!,
  url: "https://your-app.com/api/crontent/webhook",
  events: ["post.ready"],
});

Map Crontent fields to WordPress

Crontent fieldWordPress fieldNotes
titletitlePost title, rendered as H1 by theme
seo_titleYoast/RankMath meta or titlePlugin meta field; fall back to title
slugslugPost slug
excerptexcerptManual excerpt
body_mdx / body_htmlcontentHTML content block
tagstagsTag names (WP creates if missing)
hero_image_urlFeatured mediaUpload or sideload separately
canonical_urlSEO plugin metaYoast _yoast_wpseo_canonical etc.
sources[]Custom field or footer HTMLAppend cited sources
ready_atmeta.crontent_ready_atCustom post meta

Crontent runs research and produces a draft from your brief; you control angle. WordPress drafts are visible only to editors until published.

Receive and verify webhooks

Use X-Crontent-Delivery for idempotency. Crontent retries at 1m, 5m, 30m, 2h, 12h on failure.

ts
import { Crontent } from "@crontent/sdk";

const crontent = new Crontent({ apiKey: process.env.CRONTENT_API_KEY! });

export async function POST(req: Request) {
  const rawBody = await req.text();
  const deliveryId = req.headers.get("x-crontent-delivery")!;

  if (await seen(deliveryId)) return new Response("OK");

  const valid = await crontent.webhooks.verify({
    rawBody,
    signature: req.headers.get("x-crontent-signature"),
    timestamp: req.headers.get("x-crontent-timestamp"),
    secret: process.env.CRONTENT_WEBHOOK_SECRET!,
  });
  if (!valid) return new Response("Unauthorized", { status: 401 });

  const { post_id } = JSON.parse(rawBody);
  const post = await crontent.posts.get(post_id);
  await createWordPressDraft(post);
  await markSeen(deliveryId);
  return new Response("OK");
}

Create a draft in WordPress

POST to /wp-json/wp/v2/posts with Basic auth (Application Password). Set status: "draft".

ts
async function createWordPressDraft(post: Post) {
  const sourcesHtml = post.sources
    .map((s) => `<li><a href="${s.url}">${s.title ?? s.url}</a> (${s.publisher ?? ""})</li>`)
    .join("");

  const auth = Buffer.from(
    `${process.env.WP_USER}:${process.env.WP_APP_PASSWORD}`
  ).toString("base64");

  const res = await fetch(`${process.env.WP_URL}/wp-json/wp/v2/posts`, {
    method: "POST",
    headers: {
      Authorization: `Basic ${auth}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: post.title,
      slug: post.slug,
      excerpt: post.excerpt,
      content: `${post.body_html ?? post.body_mdx}<h2>Sources</h2><ul>${sourcesHtml}</ul>`,
      status: "draft",
      tags: post.tags,
      meta: {
        crontent_post_id: post.id,
        crontent_ready_at: post.ready_at,
      },
    }),
  });
  if (!res.ok) throw new Error(await res.text());
}

Register custom meta keys in functions.php or a small plugin if you need them exposed via REST.

Backfill and polling

Poll crontent.posts.list({ projectId, since, limit, cursor }) and skip posts whose crontent_post_id meta already exists. Query /wp-json/wp/v2/posts?meta_key=crontent_post_id&amp;meta_value={id}.

Official documentation