Skip to contact form
← BlogEngineeringAugust 13, 2026 · 4 min read

Stripe will send that event twice

Redelivery is ordinary traffic, not an edge case — and a customer billed once and recorded twice is worse than one not recorded at all.

hen a card clears, exactly one thing turns that payment into a customer: a webhook. It creates the client account, generates and issues a password, opens the project, seeds the payment schedule and records the money. Nothing else does any of it.

Which makes it the most expensive piece of code we own, and the one with the quietest failure mode. Get it wrong and the card is still charged. There is no error on the buyer's screen, nothing in the dashboard, and the only trace is a line in a function log nobody is reading at the time.

Stripe redelivers events after any failure, for days. That is not an exotic scenario to defend against — it is the documented behaviour, and it is a feature: it is what stops a payment being lost because your server happened to be restarting. The corollary is that your handler will run twice for the same money, and it has to be boring about it.

Everypaymentwebhookisapromisethatthesameeventcanarriveagaintomorrowandchangenothing.

We already had unit tests over the handler with a mocked database. They are useful and they cannot answer the question that matters: whether a real signed event, arriving at the real route, against a real database, leaves behind a customer who can actually log in.

So there is now a script that signs a payload exactly the way Stripe does — an HMAC over the timestamp and the body with the endpoint secret — and posts it at the running application. The signature check is satisfied rather than bypassed, and genuinely exercised: the last two assertions post a deliberately corrupted signature and check both that it is refused and that it wrote nothing.

  • The stored password is a bcrypt hash, and the account is flagged to force a change, so the generated credential cannot survive first login.
  • The instalments sum exactly to the project total — not approximately, exactly.
  • A second delivery of the identical event opens no second project, records no second payment, and creates no second client.

That last group is the one that had to be checked rather than reasoned about. It is easy to look at a handler, see a lookup by session id, and conclude it is safe. It is harder, and much more convincing, to deliver the event four times and count the rows.

Not confidence that the code is elegant. Confidence that on the day a real client's card clears — the first one, the one where nobody is watching a terminal — the thing that happens next is the thing we designed, and the thing that happens after an unremarkable network hiccup is nothing at all.

Want this kind of thinking on your project?

This is how we work through real decisions. If you're weighing a build of your own, tell us about it — we reply within one working day.