Skip to content

feat(webhooks): implement signed webhook delivery with retry and dead-letter handling#289

Open
macan88 wants to merge 3 commits intorohitdash08:mainfrom
macan88:felix/fix-77
Open

feat(webhooks): implement signed webhook delivery with retry and dead-letter handling#289
macan88 wants to merge 3 commits intorohitdash08:mainfrom
macan88:felix/fix-77

Conversation

@macan88
Copy link

@macan88 macan88 commented Mar 2, 2026

Problem

The application had no webhook infrastructure — no event emission, signed delivery, retry logic, or subscription management — making external integrations impossible.

Solution

Added app/src/lib/webhook.ts which provides the complete webhook event system:

  • Signed delivery — every POST is signed with HMAC-SHA256 over the raw body using a per-subscription secret; consumers verify via X-Webhook-Signature: sha256=<hex>.
  • Idempotency keys — a stable idempotency_key is embedded in every payload and echoed in X-Idempotency-Key header so consumers can safely deduplicate retries (at-least-once semantics documented explicitly).
  • Schema versioningpayload.version + X-Webhook-Version header; non-breaking additions keep 1.0, breaking changes bump the version with a migration window.
  • Exponential-backoff retries — up to 5 attempts with delays of 30 s / 5 min / 30 min / 2 h / 8 h.
  • Dead-letter / auto-disable — 404/410 immediately soft-disables the subscription; 5 consecutive event-level failures also soft-disable and trigger user notification.
  • Safety guards — 10 s connection timeout (AbortController) and 1 MB payload limit prevent the event loop from stalling.
  • Event catalogue — 6 typed event types: expense.created, expense.updated, expense.deleted, bill.created, bill.paid, settlement.completed.

WEBHOOK_EVENTS.md documents all event types, payload schema, signature verification, idempotency contract, retry policy, and schema versioning strategy.

Testing

  • Test 1 — verifies HMAC-SHA256 signature is a valid 64-char hex string, passes consumer verification, and fails on tampered payload or wrong secret.
  • Test 2 — mocks 5× HTTP 500; asserts 5 delivery log entries (all failed), stable idempotency key across retries, and subscription auto-disabled after exhaustion.
  • Test 3 — mocks HTTP 410; asserts exactly 1 fetch call (no retries), single failed log entry, and immediate subscription disable.

Closes #77

@macan88 macan88 requested a review from rohitdash08 as a code owner March 2, 2026 17:28
@macan88 macan88 mentioned this pull request Mar 2, 2026
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 Event System

1 participant