Docs

Webhooks

Receive signed, real-time platform events at your own HTTPS endpoints, with signature verification and automatic retries.

What webhooks are

Webhooks push events to you the moment they happen, instead of you polling the REST API. Register an HTTPS endpoint and CrunchJunkie will POST a signed JSON payload to it whenever a subscribed event fires — so you can raise a Slack alert on a visibility drop, kick off a workflow when a scan finishes, or mirror events into your own database without a cron of your own. Every delivery is signed (so you can prove it came from us), retried automatically if your endpoint is briefly down, and logged. Webhooks are available on paid plans (Pro, Agency, Scale), and only a workspace owner or admin can manage them.

Adding an endpoint

Open Settings → Webhooks and add your receiver's HTTPS URL (http:// is rejected). Choose which events it should receive — all events, or a specific subset — and optionally a label. On save we show the signing secret exactly once. It looks like whsec_… — copy it into your receiver now; we can't show it again, only rotate it for a new one. Use the "Send test" button to fire a sample event at your endpoint and confirm it accepts and verifies the delivery before you rely on it.

The event payload

We POST a JSON envelope in the Standard Webhooks shape: { "id": "msg_…", "type": "signal.created", "timestamp": "2026-08-28T10:00:00.000Z", "api_version": "2026-08-01", "data": { … } } • id — a unique message id; also sent as the webhook-id header. Use it as an idempotency key so a retry is never processed twice. • type — the event type (see below). • data — the full event object, so you rarely need a follow-up API call. Return any 2xx status to acknowledge. Anything else (or a timeout past 15 seconds) is treated as a failure and retried.

Verifying signatures

Three headers accompany every delivery: webhook-id, webhook-timestamp (unix seconds) and webhook-signature. The signature is HMAC-SHA256 over the string `{id}.{timestamp}.{body}`, base64-encoded, prefixed v1,. This is the Standard Webhooks scheme, so any svix-compatible library verifies it — or verify it by hand: import crypto from "node:crypto"; function verify(secret, headers, body) { const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64"); const signed = `${headers["webhook-id"]}.${headers["webhook-timestamp"]}.${body}`; const expected = "v1," + crypto.createHmac("sha256", key).update(signed).digest("base64"); return headers["webhook-signature"].split(" ").some(s => crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected))); } Always verify against the RAW request body (before JSON parsing) and reject deliveries whose webhook-timestamp is more than five minutes old to stop replays.

Events, retries and auto-disable

Current event types: • signal.created — a new Signal was detected (visibility drop, competitor overtake, GEO-score drop, dead source, cross-channel). Only genuinely new signals fire; a continuously-true finding won't spam you. • scan.completed — an AI-visibility scan reached a terminal state; data.status is done, stopped or error. If a delivery fails we retry with exponential backoff — after 5s, then 5m, 30m, 2h, 5h and 10h — up to eight attempts spanning more than a day; the same webhook-id is reused across every retry. After many consecutive failures an endpoint is automatically disabled (with the reason shown in Settings), so a permanently-dead URL stops generating traffic; re-enable it once it's fixed. Rotate the signing secret any time from the same screen — the old secret stops working immediately, so update your receiver in step.