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

  1. Build the endpoint. An HTTPS route that accepts POST with a JSON body.
  2. Generate a secret. A long random string (e.g. openssl rand -hex 32).
  3. Verify incoming requests with the Bearer token or the HMAC signature (above), and return 401 otherwise.
  4. Upsert by article.id and handle both "published" and "updated" events. Optionally respond with { "url": "…", "id": "…" } to record where it landed.
  5. 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
    Save, then run Test connection.

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.