Deliver articles to any CMS via webhook
When an article is published, ClimbrIQ can send a JSON payload to your endpoint. Use it to create posts in a headless CMS, trigger an internal workflow, or feed a custom publishing pipeline.
How it works
Once enabled for a brand, ClimbrIQ makes an HTTP POST request to your webhook URL each time an article is published, and again each time a published article is refreshed. The event field tells you which: publish for a new article, update for a new version of one you already have. Match updates to the original post by articleId.
ClimbrIQ treats any 2xx response as success. Anything else is a failed delivery. A failed publish leaves the article unpublished in ClimbrIQ; a failed update is still saved in ClimbrIQ. Scheduled deliveries are retried automatically with backoff, and the owner is emailed if they keep failing.
Request format
Method
POST
Content type
application/json
Headers
Content-Type— alwaysapplication/jsonX-Webhook-Secret— the secret configured for the brand. Omitted when no secret is set.X-Idempotency-Key— for a publish, the same on every retry, so you can ignore a key you have already processed. Each update gets a new key (a retried update may arrive with a different one), so always apply updates byarticleId— applying the same update twice is harmless.
Payload (example)
{
"event": "publish",
"articleId": "3f1c2a9e-5b7d-4c1e-9a2b-8d6f0e4c7b21",
"title": "How to Choose a Local Plumber",
"metaTitle": "How to Choose a Local Plumber in the UK",
"metaDescription": "What to check before you book, from registration and insurance to written quotes.",
"content": "<article body as markdown, without the title>",
"html": "<the same body rendered to HTML>",
"slug": "how-to-choose-a-local-plumber",
"excerpt": "A concise one-sentence summary of the article.",
"featureImageUrl": "https://… or null"
}titleis the article's headline. It is not repeated incontentorhtml, so render it as the post title.metaTitleandmetaDescriptionare for your SEO title and description fields.- Use
contentif your CMS takes markdown, orhtmlif it takes HTML. Both are ready to publish as they are.
Authentication
When you set a secret, ClimbrIQ sends it as X-Webhook-Secret with every request. Validate it on your endpoint before processing the payload.
const secret = request.headers.get('X-Webhook-Secret');
if (secret !== process.env.EXPECTED_SECRET) {
return new Response('Forbidden', { status: 403 });
}Example receiver (Next.js Route Handler)
// app/api/climbriq-webhook/route.ts
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const secret = request.headers.get('X-Webhook-Secret');
if (secret !== process.env.CLIMBRIQ_WEBHOOK_SECRET) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// A retry of a delivery you already handled: acknowledge and stop.
const key = request.headers.get('X-Idempotency-Key');
if (key && (await yourStore.hasProcessed(key))) {
return NextResponse.json({ received: true });
}
const body = (await request.json()) as {
event: 'publish' | 'update';
articleId: string;
title: string;
metaTitle: string;
metaDescription: string;
content: string;
html: string;
slug: string;
excerpt: string;
featureImageUrl: string | null;
};
const post = {
title: body.title,
body: body.html, // or body.content if your CMS takes markdown
slug: body.slug,
excerpt: body.excerpt,
seoTitle: body.metaTitle,
seoDescription: body.metaDescription,
imageUrl: body.featureImageUrl,
};
// Keyed on articleId, so a refresh updates the post instead of duplicating it.
const existing = await yourCms.findPostByExternalId(body.articleId);
if (existing) {
await yourCms.updatePost(existing.id, post);
} else {
await yourCms.createPost({ ...post, externalId: body.articleId });
}
if (key) await yourStore.markProcessed(key);
return NextResponse.json({ received: true });
}Want the easiest setup?
If you’re on WordPress, the plugin-based integration is usually the fastest route.