crontent
Integration·Hygraph·intermediate

Automated blog posts for Hygraph

Automated blog posts for Hygraph: connect Crontent webhooks and create draft Hygraph entries with the Content API.

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

Verify the Crontent webhook, then create a Hygraph draft through the Content API with mapped titles, body, and sources.

What you need

  • CRONTENT_API_KEY and CRONTENT_WEBHOOK_SECRET
  • Hygraph project with a Post model and Content API permanent auth token
  • Webhook endpoint with @crontent/sdk and a GraphQL client (or fetch)

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 Hygraph

Crontent fieldHygraph fieldNotes
titletitleString, display H1
seo_titleseoTitleMeta title; fall back to title
slugslugUnique string
excerptexcerptString
body_mdx / body_htmlcontentRich text (convert HTML to AST)
tagstagsString list or relation
hero_image_urlheroImageAsset or URL
canonical_urlcanonicalUrlString
sources[]sourcesJSON or component model
ready_atcrontentReadyAtDateTime

Crontent researches and drafts on your schedule; you set angle in the brief. Hygraph draft stages keep content out of the published API until release.

Receive and verify webhooks

Deduplicate on X-Crontent-Delivery. Failed deliveries retry at 1m, 5m, 30m, 2h, 12h.

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 createHygraphDraft(post);
  await markSeen(deliveryId);
  return new Response("OK");
}

Create a draft in Hygraph

Use a Content API mutation. Enable draft stages in your model, then create without publishing.

ts
async function createHygraphDraft(post: Post) {
  const mutation = `
    mutation CreatePost($data: PostCreateInput!) {
      createPost(data: $data, stage: DRAFT) {
        id
        slug
      }
    }
  `;

  const res = await fetch(process.env.HYGRAPH_CONTENT_API!, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.HYGRAPH_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query: mutation,
      variables: {
        data: {
          title: post.title,
          seoTitle: post.seo_title ?? post.title,
          slug: post.slug,
          excerpt: post.excerpt,
          content: { html: post.body_html },
          tags: post.tags,
          sources: post.sources,
          crontentPostId: post.id,
        },
      },
    }),
  });
  if (!res.ok) throw new Error(await res.text());
}

Adjust stage: DRAFT and field names to match your schema and stage configuration.

Backfill and polling

Poll crontent.posts.list({ projectId, since, limit, cursor }) and skip entries with matching crontentPostId. Useful for recovery after downtime.

Official documentation