Docs / Publish to your CMS

Webhooks & Feeds

Three ways to integrate a site or workflow that isn't one of the built-in CMS platforms:

  • Custom webhook CMS connection - we push each publish to your endpoint; your code writes it into your system. Use this when your site should be the destination.
  • Webhook event subscriptions - we notify your endpoint about content lifecycle events (works great with Zapier, Make, and n8n). Use this for notifications and automations alongside any CMS.
  • Feeds & content API - your site pulls published content from us via RSS, JSON Feed, or a read-only JSON API. Use this when you'd rather poll than receive.

The public webhook documentation page

The custom webhook CMS connection

Add a connection with provider webhook (Settings → CMS connections → Add), enter your HTTPS endpoint URL, and optionally a signing secret. Saving sends a signed ping you can use to verify your receiver.

On each publish, your endpoint receives a POST:

{
  "event": "publish",
  "target": "production",
  "document": {
    "slug": "your-slug",
    "title": "SEO title",
    "h1": "Page heading",
    "bodyMarkdown": "## Clean canonical markdown…",
    "bodyHtml": "<h2>Rendered HTML…</h2>",
    "seoDescription": "…",
    "shortDescription": "…",
    "resourceType": "Guides",
    "authorName": "Jane Doe",
    "date": "2026-07-01",
    "hero": { "filename": "hero.png", "url": "https://…/pub/a/…/hero.png", "alt": "…" },
    "bodyAssets": [{ "filename": "body1.png", "url": "https://…", "alt": "…" }]
  },
  "sentAt": "2026-07-01T12:00:00.000Z"
}
  • Image URLs are stable, content-addressed, and publicly fetchable - download them into your own storage or serve them directly.
  • Unpublishes send { "event": "unpublish", "externalId": "…", "target": "…" }.
  • Respond with 2xx to acknowledge. Optionally return { "id": "your-entry-id", "url": "https://your-site/post" } and we'll track that ID and link the live URL in the dashboard.

Webhook event subscriptions

Separate from CMS connections: Settings → Webhooks → Add subscribes any endpoint to lifecycle events.

EventFires when
content.publishedAn article goes live for the first time
content.updatedA live article is re-published with changes
content.unpublishedAn article is taken offline
draft.ready_for_reviewA draft is moved to review
network.placement.proposedA Link Network placement is proposed for your site to host
network.placement.liveA placement's link is embedded and live
network.placement.verifiedA live placement passed on-page verification
network.placement.brokenA previously verified placement failed verification
network.placement.removedA placement was removed

Each delivery POSTs a versioned JSON payload with the event name, the destination provider and external ID/URL, the owning strategy, and the full prepared document (markdown, HTML, and image URLs). Delivery headers:

  • X-Waveform-Event - the event name
  • X-Waveform-Delivery - a unique delivery ID (deduplicate on this)

Verifying signatures

Set a signing secret on the subscription (or webhook connection) and every request carries:

  • X-Waveform-Timestamp - unix milliseconds when the request was signed
  • X-Waveform-Signature-V2 - sha256=<HMAC-SHA256(secret, "{timestamp}.{body}")>

Verify the signature and reject stale timestamps (older than ~5 minutes) to prevent replay:

import crypto from "node:crypto";

function verify(req, rawBody, secret) {
  const ts = req.headers["x-waveform-timestamp"];
  const sig = req.headers["x-waveform-signature-v2"];
  if (!ts || !sig || Date.now() - Number(ts) > 5 * 60_000) return false;
  const expected = "sha256=" +
    crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

(A legacy X-Waveform-Signature header signs the body alone, for receivers built before timestamps were added. Prefer V2.)

Delivery & retries

Non-2xx responses and timeouts are retried with exponential backoff - 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours - for up to 6 total attempts. Respond 200 quickly and process asynchronously.

Every delivery (with status, attempts, and the endpoint's last response code) is visible under Settings → Webhooks → Deliveries, where you can also redeliver manually.

Feeds & content API

Settings → Feeds & content API generates a workspace token that gates three read-only endpoints:

  • RSS: /feeds/{token}/rss.xml
  • JSON Feed: /feeds/{token}/feed.json
  • Content API: /api/content/{token} (list) and /api/content/{token}/{slug} (single entry with full markdown/HTML)

Rotate the token at any time; old URLs stop working immediately. This is the simplest way to syndicate published content into newsletters, aggregators, or a site that prefers pulling on its own schedule.