Skip to content

Add delivery ID and attempt headers to outbound webhooks - #222

Merged
morcen merged 1 commit into
mainfrom
fix/issue-43-webhook-delivery-id-header
Sep 12, 2026
Merged

Add delivery ID and attempt headers to outbound webhooks#222
morcen merged 1 commit into
mainfrom
fix/issue-43-webhook-delivery-id-header

Conversation

@morcen

@morcen morcen commented Sep 11, 2026

Copy link
Copy Markdown
Owner

What was broken

SendWebhook never sent any per-delivery or per-attempt identifier to the destination endpoint (app/Jobs/SendWebhook.php). The outbound request only carried:

Content-Type: application/json
X-Webhook-Secret: sha256=<hmac>
X-Webhook-Event: <event name>
User-Agent: Webhook-Management-Platform/1.0

Without a stable delivery identifier, a receiving endpoint has no way to recognize that two HTTP calls correspond to the same logical delivery — whether caused by a manual "retry" via POST /api/v1/deliveries/{id}/retry, an ordinary network-level retry, or the scheduled webhooks:process-retries retry path. This is standard practice for webhook platforms (GitHub, Stripe, etc. all send a delivery/event ID) specifically so consumers can implement idempotent processing. Without it, any duplicate delivery silently causes the receiver to process the same event twice (e.g. double-charging, double-provisioning), with no way for the receiving side to defend against it.

What changed

  • Added an X-Webhook-Delivery-Id header (the delivery's stable ID — the same value across every attempt for a given delivery) to every outbound webhook request.
  • Added an X-Webhook-Attempt header (the current attempt number, incrementing on each retry) alongside it.
  • Documented both new headers in WEBHOOK_README.md's "Webhook Security" section, next to the existing X-Webhook-Secret/X-Webhook-Event headers.
  • Added tests/Feature/SendWebhookDeliveryIdHeaderTest.php, covering:
    • both headers are present and correctly valued on a single delivery attempt
    • the delivery ID stays stable while the attempt number increments across a simulated retry

Testing

  • vendor/bin/pint --dirty — clean
  • composer test (full suite) — 248 passed, 1 pre-existing skip, 0 failures

Fixes #43


🤖 Generated with Claude Code

https://claude.ai/code/session_01VhUzR11zCCYyxBu4ixbpjF


Generated by Claude Code

Webhook deliveries carried no per-delivery identifier, so a receiving
endpoint had no way to recognize that two HTTP calls (a manual retry,
a network-level retry, or a scheduled retry) corresponded to the same
logical delivery, leaving them unable to dedupe and risking duplicate
side effects like double-charging or double-provisioning.

Add an X-Webhook-Delivery-Id header (the delivery's stable ID) and an
X-Webhook-Attempt header (the current attempt number) to every
outbound webhook request in SendWebhook, and document both in
WEBHOOK_README.md's Webhook Security section alongside the existing
X-Webhook-Secret/X-Webhook-Event headers.

Fixes #43

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VhUzR11zCCYyxBu4ixbpjF
@morcen
morcen merged commit 3f9e2f3 into main Sep 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Webhook deliveries carry no idempotency/delivery identifier, so receivers can't dedupe retries

1 participant