Skip to content
Muhammet Şafak
tr
Asked by: Sıla Answered:

The payment webhook keeps re-sending the same notification; how do I set up idempotency?


Question

I receive webhooks from a third-party payment provider. Because of network outages, the provider may send the same successful payment notification multiple times (retries). How should I architect the webhook endpoint so my system doesn't double-process (double-spending)? How should request signing (HMAC) and DB-level idempotency key tracking work?

Answer

Short answer: these are two separate concerns — authenticity (HMAC) and preventing duplicates (idempotency). Don’t conflate them; solve each separately.

Short answer

In one sentence: the signature asks “is this really the provider”, idempotency asks “have I done this work before”. I wrote about the general form of the second one — the API-side rules for safely repeating the same request — in idempotency in APIs; this is that idea applied to a webhook.

Why

  1. Signatures and uniqueness guarantee different things. A correctly signed notification can arrive five times; the signature tells you it’s genuine, not which delivery it is.

  2. App logic can’t give you a once-only guarantee. If two requests arrive at the same time, “check then write” lets both through. Only the database’s own constraint gives you uniqueness.

  3. A slow response manufactures more retries. If the provider times out it resends the same notification; a synchronous handler enlarges its own problem.

What to do

  1. Verify the signature over the RAW body. Check the HMAC signature against the raw body, before parsing, with a constant-time compare. Decode and re-encode the JSON and you break the signature.

  2. Let the DB enforce idempotency. Persist the provider’s event id (or a hash) in a table with a UNIQUE constraint; do a check-or-insert inside a transaction.

  3. Return 200 fast first, enqueue the work. Verify the signature, record it, return 200 fast, and push the real work to a queue; keep the worker idempotent on the same key. The operational detail of the queue side is in the Laravel queue and Supervisor post.

  4. Be safe to call twice at every layer. The handler, the worker, and the side effect must each produce one clean result when called twice with the same notification.

  5. Watch ordering and the replay window. A “refund” notification can arrive before its “charge”; handle out-of-order cases.

Bottom line: I’d build the flow as verify signature → return 200 fast → enqueue the work, and make the DB enforce idempotency with a UNIQUE constraint on event_id + a transactional check-or-insert. Compare the HMAC constant-time over the raw body. Double-spending is stopped by the database’s uniqueness guarantee, not by app code.

Related Reading

Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

More Questions

All questions

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind