Guides

Outbound events, verified

Subscribe, verify HMAC signatures, and know the retry policy. Needs the monitoring:write scope.

Updated 2026-11-08

On this page

Guide · webhooks

Subscribe, verify with HMAC, and know the retry policy. Needs the monitoring:write scope.

  1. Subscribe your endpoint

    One subscription per URL; events are audit.finished and update.p0.

    Shell
    curl -s -X POST "$API/v1/ai/subscriptions" -H "Authorization: Bearer $SEO_WATCH_KEY" \
      -H 'Content-Type: application/json' \
      -d '{"url":"https://example.com/hooks/seo-watch","events":["audit.finished","update.p0"]}'
  2. Verify the signature

    Every delivery carries X-Seo-Watch-Event and an HMAC-SHA256 signature in X-Seo-Watch-Signature (t=...,v1=...). Compute v1 over the raw request bytes — never the re-serialized JSON — as HMAC-SHA256(secret, `${t}.${event}.${rawBody}`): the signed string binds the timestamp, the event header, and the unparsed body. Also reject deliveries whose t is more than 300 seconds from your clock.

    TypeScript
    import { createHmac, timingSafeEqual } from "node:crypto";
    
    const [t, v1] = signatureHeader.replace(/^t=/, "").split(",v1=");
    const expected = createHmac("sha256", secret)
      .update(`${t}.${eventHeader}.${rawBody}`)
      .digest("hex");
    
    const fresh = Math.abs(Date.now() / 1000 - Number(t)) <= 300;
    const ok = fresh && timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
  3. Expect retries, then DLQ

    Failing endpoints are retried 3 times, then the message lands in the dead-letter queue. 5xx from your endpoint never counts against your API quota.

    Shell
    # list and prune when you rotate endpoints
    curl -s "$API/v1/ai/notify-subscriptions" -H "Authorization: Bearer $SEO_WATCH_KEY"
    curl -s -X DELETE "$API/v1/ai/notify-subscriptions/:id" -H "Authorization: Bearer $SEO_WATCH_KEY"