Connect a webhook
The webhook is the path for any coded site that wants to receive PilotScribe articles and store/render them itself — your own app, a custom backend, a static-site rebuild hook. On every publish or update we POST the article as JSON to an HTTPS endpoint you control.
Prefer to pull instead of receive a push? Use the headless Content API — or let an agent wire it up via the MCP.
What you'll need
- A public HTTPS endpoint that accepts
POST(no localhost / private IPs — we refuse non-public targets). - A shared secret you generate (a long random string). You'll paste it into PilotScribe and use it to verify requests.
What we send
Body — an event plus the full article:
POST https://your-site.com/pilotscribe-webhook
{
"event": "published", // or "updated"
"article": {
"id": "ai-writing-tools", // = slug; your upsert key
"title": "AI writing tools…",
"slug": "ai-writing-tools",
"status": "published", // or "draft"
"metaDescription": "…",
"mdx": "# Raw markdown…", // raw source
"html": "<p>…</p>", // sanitized, ready to render
"imageUrl": "https://…",
"imageAlt": "…"
}
} Headers — the secret is sent two ways so you can verify either:
Authorization: Bearer <your-secret>
Content-Type: application/json
X-PilotScribe-Event: published // or "updated"
X-PilotScribe-Signature: sha256=<hmac-sha256 of the raw body> html is already sanitized and ready to render;
mdx is the raw Markdown if you'd rather render it
yourself. We upsert by article.id (the slug), so the same
slug on "updated" should overwrite, not duplicate.
Verify the request (recommended)
Check the HMAC-SHA256 signature over the exact raw
body (or, more simply, compare the Bearer token). Example
with Express:
import crypto from "node:crypto";
const SECRET = process.env.PILOTSCRIBE_WEBHOOK_SECRET;
// Use the RAW body — re-serializing JSON would break the signature.
app.post("/pilotscribe-webhook",
express.raw({ type: "application/json" }), (req, res) => {
const sig = req.get("X-PilotScribe-Signature") || "";
const expected = "sha256=" +
crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const ok = sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) return res.status(401).end();
const { event, article } = JSON.parse(req.body.toString());
// Upsert by article.id (the slug); render article.html or article.mdx.
// ...save to your DB / site...
// Optional: tell PilotScribe where it landed.
res.json({ url: "https://your-site.com/blog/" + article.slug, id: article.id });
}); Steps
- Build the endpoint. An HTTPS route that accepts
POSTwith a JSON body. - Generate a secret. A long random string (e.g.
openssl rand -hex 32). - Verify incoming requests with the Bearer token or the HMAC signature (above), and return
401otherwise. - Upsert by
article.idand handle both"published"and"updated"events. Optionally respond with{ "url": "…", "id": "…" }to record where it landed. - Paste into PilotScribe. In
app.pilotscribe.com, open
your site, go to the Publishing tab, choose
Webhook, then enter:
- Webhook URL → your HTTPS endpoint
- Secret → the value you generated
Good to know
- HTTPS & public only. We block non-public hosts (SSRF protection) and don't follow redirects — a 3xx fails rather than forwarding your secret elsewhere.
- Idempotency. Key everything on
article.id(the slug) so re-sends and updates don't create duplicates. - Raw body for HMAC. Verify against the exact bytes received; re-serializing the JSON changes the signature.
Hitting a wall? See Troubleshooting, or go back to all connect guides.