SEO for SaaS
Start
API reference

Webhooks

A signed POST when an article is published, changed, or unpublished, and how to verify it.

Set a URL in Delivery → Webhook. SEO for SaaS sends a POST to it:

eventWhen
content.createdAn article is published without going through review.
content.updatedA published article changed: rewritten, edited, approved from review, or unpublished. The slug and URL stay the same.
webhook.testYou select Send test event. No article changed, so there's nothing to refresh.

Refresh on both content events: an article approved from the review queue arrives as content.updated.

The body only names the event, so read articles from the API, never from the payload:

json
{ "event": "content.created", "projectId": "…" }

The test event adds a timestamp and a message:

json
{ "event": "webhook.test", "projectId": "…", "timestamp": "2026-09-29T12:00:00.000Z", "data": { "message": "Test event from the dashboard. No content changed." } }

Verify, then act

We sign every delivery with Standard Webhooks and the signing secret (Delivery → Webhook → Signing secret → Reveal). Signing starts the first time you reveal the secret. Until then, deliveries go out unsigned, and a verifying endpoint rejects them. Verify against the raw body, exactly as received: re-serialized JSON won't match.

The TypeScript tab uses the SEO for SaaS handler, lib/seoforsaas/webhook.ts, a fetch-style function for Next.js route handlers, Astro endpoints, Hono, Bun, Deno, and Cloudflare Workers. It reads SEOFORSAAS_WEBHOOK_SECRET from process.env; where there's none, pass the secret: seoforsaasWebhook(onChange, { secret: import.meta.env.SEOFORSAAS_WEBHOOK_SECRET }) in Astro, the env binding in Workers.

npx shadcn@latest add https://seoforsaas.dev/r/webhook.json
POST <your webhook URL>content-type: application/jsonwebhook-id: msg_…webhook-timestamp: 1759132800webhook-signature: v1,…{ "event": "content.created", "projectId": "YOUR_PROJECT_ID" }

Delivery

  • We wait up to 5 seconds and follow no redirects. Any 2xx counts as delivered. Delivery → Recent deliveries lists every call and its response for 15 days. We don't retry a failed delivery. To resend it, select Send again in its row menu.

  • Treat a delivery as a nudge to refresh: your next build or request reads the current articles from the API, so a missed delivery costs freshness, never data.

  • Rolling the secret (Webhook → Roll): for 24 hours every delivery carries signatures from both the old and the new secret, so update your site within that window.

  • A static site needs no endpoint: paste your host's deploy hook instead. See Static sites.