Skip to content

Feature/webhook signing dedup dlq - #413

Merged
therealjhay merged 2 commits into
Betta-Pay:mainfrom
Omoboi-dev:feature/webhook-signing-dedup-dlq
Jul 28, 2026
Merged

Feature/webhook signing dedup dlq#413
therealjhay merged 2 commits into
Betta-Pay:mainfrom
Omoboi-dev:feature/webhook-signing-dedup-dlq

Conversation

@Omoboi-dev

Copy link
Copy Markdown
Contributor

Summary

Closes #351
Closes #352
Closes #353
Closes #354

Adds intelligent startup ledger discovery, HMAC-SHA256 webhook signing, composite unique constraint for event deduplication, and a dead-letter queue for failed webhook deliveries.


Changes

#352 — Intelligent startup ledger discovery

  • Fresh deployments start from max(1, tip - INITIAL_BACKFILL_LEDGERS) (default 1000) instead of ledger 1
  • Existing deployments resume from the latest indexed event + 1
  • INDEX_FROM_LEDGER env var for manual override
  • Falls back to ledger 1 if Stellar RPC is unavailable at startup
  • 8 unit tests covering fresh DB, resume, manual override, RPC failure, floor-at-1, and invalid config

#353 — Composite unique constraint for event deduplication

  • Replaced stellarId @unique with @@unique([stellarId, contractId, ledger]) composite constraint on IndexedEvent
  • persistEvent now catches Prisma P2002 errors and skips duplicates with a debug log instead of crashing
  • Replay worker also handles P2002 gracefully
  • New migration: 20260728120000_add_composite_unique_indexed_event

#351 — HMAC-SHA256 webhook payload signing

  • Added signPayload(body, secret) to @bettapay/webhook-delivery producing t={unix_ts},s={hex_hmac} format
  • WebhookJobData accepts optional signingSecret
  • Worker includes X-BettaPay-Signature header when secret is present
  • Indexer passes signingSecret from WebhookSubscription to each delivery job
  • Backward compatible: unsigned webhooks work exactly as before
  • 6 new tests covering signature format, recomputation, and header presence

#354 — Dead-letter queue for failed webhook deliveries

  • Added indexer-webhooks-dlq BullMQ queue
  • Worker failed event handler moves jobs to DLQ after all retries exhausted
  • GET /api/admin/webhooks/dead-letter — lists DLQ entries with pagination
  • POST /api/admin/webhooks/dead-letter/:id/replay — re-queues a DLQ entry to the main delivery queue and removes it from DLQ
  • Both endpoints require x-service-token auth
  • Updated webhook-delivery to support removeOnFail: false for keeping all failed jobs

- Betta-Pay#353: Replace stellarId @unique with @@unique([stellarId, contractId, ledger])
  composite constraint. Persist event catches P2002 and skips duplicates.
- Betta-Pay#351: Add HMAC-SHA256 payload signing via signPayload(). Worker includes
  X-BettaPay-Signature header when signingSecret is set on the subscription.
- Betta-Pay#354: Add dead-letter queue (indexer-webhooks-dlq) for webhook jobs that
  exhaust all retries. Admin endpoints GET /api/admin/webhooks/dead-letter
  and POST /api/admin/webhooks/dead-letter/:id/replay.
- Fresh deployments start from max(1, tip - INITIAL_BACKFILL_LEDGERS)
  instead of ledger 1, avoiding hours of catch-up.
- Existing deployments resume from the latest indexed event + 1.
- INDEX_FROM_LEDGER env var for manual override.
- Falls back to ledger 1 if Stellar RPC is unavailable at startup.
- 8 unit tests covering fresh DB, resume, manual override, RPC failure,
  floor-at-1, and invalid config edge cases.
@drips-wave

drips-wave Bot commented Jul 28, 2026

Copy link
Copy Markdown

@Omoboi-dev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@therealjhay
therealjhay merged commit 1d62018 into Betta-Pay:main Jul 28, 2026
3 of 6 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

2 participants