Docs

Webhooks

Call your own URL when content is published or deleted. Every payload is signed, failed deliveries are retried, and recent deliveries are logged.

Set up

Settings → API → Webhooks → Add webhook (admins only). Enter the URL; Orbiter shows the signing secret once — store it where your receiver can read it. Use Test to send a ping.

Events

EventWhen
publishAn entry is published (editor, bulk action, or scheduled publishing)
reviewAn entry was submitted for review (review workflow)
deleteEntries are deleted (bulk)
pingThe Test button

Request

POST /your/endpoint
Content-Type: application/json
X-Orbiter-Event: publish
X-Orbiter-Delivery: 6f1c…           (unique per delivery, same across retries)
X-Orbiter-Timestamp: 1790000000      (unix seconds)
X-Orbiter-Signature: sha256=9a3f…

{"event":"publish","id":"6f1c…","timestamp":"2026-10-08T09:00:00.000Z","collection":"posts","slug":"hello"}

Verify the signature

The signature is HMAC-SHA256(secret, timestamp + "." + rawBody), hex-encoded. Compute it over the raw request body, compare in constant time, and reject old timestamps to stop replays.

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(rawBody, headers, secret) {
  const ts = headers['x-orbiter-timestamp'];
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;   // 5 min
  const expected = 'sha256=' + createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
  const given = String(headers['x-orbiter-signature'] ?? '');
  return given.length === expected.length &&
         timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}

Ready-made receivers

Slack or Telegram on review, IndexNow, Cloudflare purge, Mastodon: see Webhook recipes.

Retries and log

Anything other than a 2xx answer (or a timeout after 10 s) is retried after 5 seconds, 30 seconds and 5 minutes. Redirects are not followed. The last 50 deliveries — status, attempt, error — are listed under Recent deliveries. Retries run in the server process, so a restart drops pending ones.

Older webhooks created before signing existed keep working but are marked unsigned. Remove and re-add them to get a secret. The secret is encrypted at rest when ORBITER_SECRET is set.

Last updated Edit this page on GitHub ↗