You’ll need
- An API key with
webhooks:writeandwebhooks:read. - A public HTTPS URL. Private, loopback, and metadata IPs are blocked at both create time and every delivery.
1. Create a subscription
Savesigning_secret from the response. Meow returns it once.
Every webhook POST requires an Idempotency-Key header, so a retried
create never leaves you with two subscriptions. Generate a fresh key per
operation: 1-50 printable ASCII characters, no spaces. Reusing one is
rejected with a 400.
Response
2. Verify the signature
Three headers come with every delivery:
Sign
f"{webhook-id}.{webhook-timestamp}.{body}" with HMAC-SHA-256. The
HMAC key is the base64-decoded body of your whsec_<base64> secret,
not the raw string. Reject anything more than 5 minutes off. That is the
replay window.
3. Dispatch on payload.status
Event names stay coarse: {resource}.created and {resource}.updated.
The lifecycle stage lives on the payload, so one handler covers the whole
flow:
4. Pick a payload mode
Setpayload_mode per subscription. Change it any time with PATCH.
- snapshot (default)
- thin
data carries the full resource. No follow-up GET needed.5. Handle out-of-order deliveries
Deliveries are not ordered. Retries, redrives, and concurrent workers mean an older state for a resource can land after a newer one. Two fields let you stay correct. Every resource event carries asequence, a counter that increments
once per resource, the same for every subscriber, stable across retries and
redrives. Track the highest sequence you’ve applied per resource and
advance it with a single atomic conditional write, so a stale delivery
can’t overwrite newer state:
sequence is absent on webhook.test and message.attempt.exhausted (they
describe no resource). For everything else, pair it with deduplication on
webhook-id (next section): sequence discards stale states, webhook-id
discards exact duplicates.
6. Retries
Meow retries up to 10 times over ~91 hours.- Each non-zero delay gets up to 20% extra jitter.
Retry-Afteron a 429 or 503 is honored, up to 24 hours.- 5 failures in a row open a circuit breaker on the subscription. New deliveries wait out a cooldown (1 → 30 min) without burning an attempt. A success closes it.
- After 10 failed attempts the subscription is auto-disabled
(
disabled_reason=retry_exhausted) and amessage.attempt.exhaustedevent fires.
PATCH /webhooks/subscriptions/{id} with {"is_enabled": true},
or replay a single delivery with POST /webhooks/deliveries/{id}/redrive.
7. Operate
Send a test event
Sends
webhook.test to one subscription. Good for verifying a new
receiver without waiting for real activity.Inspect delivery history
Every attempt with HTTP status and response body excerpt.
Redrive a delivery
Resets the counter and re-queues. Works even after
failed_permanent.Rotate the secret
PATCH with rotate_secret: true. Both old and new secrets sign for
7 days; pick whichever your code recognizes.8. Security checklist
Verify the signature
Verify the signature
Constant-time compare.
== leaks the secret.Reject timestamps older than 5 minutes
Reject timestamps older than 5 minutes
Otherwise a leaked payload can be replayed forever.
Dedupe on `webhook-id`
Dedupe on `webhook-id`
Same event can arrive twice (retries, redrives).
webhook-id doesn’t change.Drop stale states with `sequence`
Drop stale states with `sequence`
Advance the highest
sequence per resource with an atomic conditional
write so a stale delivery can’t overwrite newer state.
See Handle out-of-order deliveries.See also
- Event catalog: payload shape per event.
- Standard Webhooks: the wire format.
POST /webhooks/subscriptions: full subscription contract.