Webhook delivery log + replay (admin)
Last updated: April 2026
Every inbound webhook from Stripe, Stripe Connect, Clerk, Shopify, and Meta lands in a global delivery log before it touches your merchant data. Platform admins can browse the log, drill into the raw payload, and replay any failed delivery from /admin/webhooks.
What gets logged
- Source (stripe, stripe_connect, clerk, shopify, meta)
- Event type (checkout.session.completed, products/update, etc.)
- External event id (used for dedupe and replay correlation)
- Resolved merchant id when known (Clerk org webhooks may not have one yet)
- Whether the signature verified (HMAC for Stripe/Shopify, svix for Clerk)
- Raw request body (JSON) and a curated set of headers
- Final HTTP status, processing duration, and error message on failure
- Replay count and last-replayed-at timestamp
Status badges
- received (blue) — landed but not yet finished processing
- processed (green) — handler ran to completion with a 2xx response
- failed (red) — signature was bad, an exception was thrown, or the handler returned 4xx/5xx
- replayed (amber) — a manual replay completed successfully
Replay rules
Replay rebuilds the original Request from the stored payload + headers and pipes it back through the same route handler. The handler always re-verifies the signature, so:
- A delivery with signature_valid=0 cannot be replayed — the original signature would re-fail. The Replay button is hidden for these.
- Replay is destructive — handlers re-run side effects (DB writes, email sends, store-credit grants). Most are idempotent (we dedupe by event id), but verify before replaying twice in quick succession.
- Replay does NOT re-deliver the webhook to its original sender — it only re-invokes our internal handler.
Common failure causes
- Bad signature — webhook secret mismatch (rotated key, wrong env), stale clock, or genuinely malformed delivery
- Unknown merchant — Clerk organization.created webhook fired before the org_subscriptions row existed; replay after fixing
- D1 timeout — downstream merchant DB unavailable for >5s; replay once D1 is healthy again
- Schema mismatch — handler tried to write a column that does not exist; deploy the migration first, then replay
- Stripe Connect missing account — the connected account was deleted before the event arrived; cannot recover, mark resolved
Privacy + retention
Stored payloads include PII — Stripe customer email, Shopify customer name + address, Clerk identity claims. Treat the log as restricted data. Only platform_admin users can read it.
There is no auto-purge yet — the log grows unbounded. We will add a scheduled-task purge once row count crosses ~1M. If you need an emergency wipe of old rows, run a manual DELETE against DB_GLOBAL.webhook_deliveries WHERE received_at < ? in the Cloudflare D1 console.
Payload-redaction in the detail viewer (mask email/phone/etc.) is a v2 enhancement. For now, treat the modal as you would the Stripe Dashboard.
Was this article helpful?