Skip to content

fix(bridge): dedupe KYC webhook deliveries — CAS transitions + lock-first#434

Merged
islandbitcoin merged 1 commit into
mainfrom
jabariennis/bridge-webhook-dedupe
Jul 6, 2026
Merged

fix(bridge): dedupe KYC webhook deliveries — CAS transitions + lock-first#434
islandbitcoin merged 1 commit into
mainfrom
jabariennis/bridge-webhook-dedupe

Conversation

@islandbitcoin

Copy link
Copy Markdown
Contributor

Summary

First live KYC test on TEST produced a duplicated "identity verification approved" push: customer.created raced customer.updated, both read the pre-update status, both notified. Bridge's delivery retries would do the same in prod.

  • Atomic CAS transition (transitionBridgeKycStatus): status update guarded on != next, reporting whether this call won. Notifications and side effects (virtual-account auto-create) only fire for the winner — races and retries collapse to exactly one of each. The five copy-pasted status branches are unified on one flow.
  • Lock-first: the event_id idempotency lock ran at the end of the kyc handler (bookkeeping only); it now runs before any reads/writes. Same fix applied to the external-account route, which persisted before checking. transfer already correct; deposit deliberately structured and untouched.

Test plan

  • 8 kyc handler tests (2 new: race-loser skips notification + VA creation; duplicate event_id short-circuits before account lookup)
  • 87 webhook-server unit tests green; tsc clean

Found during flags-on TEST validation (sandbox auto-approval surfaced the race on the very first event).

🤖 Generated with Claude Code

…irst

First live KYC test on TEST fired the "approved" push twice: two
distinct events (customer.created racing customer.updated) both read
the pre-update status, passed the previousStatus guard, and notified.
Bridge delivery retries would duplicate pushes the same way in prod.

Two-layer fix:

1. Atomic status transition (new AccountsRepository
   transitionBridgeKycStatus): findOneAndUpdate guarded on
   bridgeKycStatus != next, reporting whether THIS call changed it.
   The kyc handler now gates notifications AND side effects (virtual
   account auto-creation) on winning the transition — racing deliveries
   collapse to exactly one notification and one side-effect pass.
   All five status branches unified on the same flow.

2. Lock-first: the event_id idempotency lock was taken at the END of
   the handler — pure bookkeeping that prevented nothing. It now runs
   before any reads or writes. Same late-lock bug fixed in the
   external-account route (it persisted the account before checking);
   transfer already locked first, deposit is deliberately structured
   (audit outside, credit inside its lock) and left alone.

8 kyc handler tests (2 new: race-loser skips side effects; duplicate
event_id short-circuits before touching the account); 87 webhook-server
tests green.

Refs the ENG-461/flags-on TEST validation session.
@islandbitcoin islandbitcoin merged commit 080cd8a into main Jul 6, 2026
14 checks passed
@islandbitcoin islandbitcoin deleted the jabariennis/bridge-webhook-dedupe branch July 6, 2026 21:44
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.

2 participants