Stripe & Billing

Stripe Webhook Idempotency in Node: Stop Double Work

Stripe webhook idempotency is not just a retry header. In Node, the reliable pattern starts with raw-body signature verification and ends with a durable event ledger.

Key takeaways for idempotent Stripe webhooks

  • Stripe Docs says webhook signature verification requires the request body string exactly as Stripe sent it. [2]
  • Stripe's Node webhook example uses stripe.webhooks.constructEvent(request.body, sig, endpointSecret) before handling the event. [1]
  • Stripe Docs includes duplicate-event handling guidance: log processed event IDs and skip events already processed. [1]
  • Stripe Docs says API idempotency keys make repeated API requests return the same result, including failures. [3]
  • Stripe Docs says an Event object represents activity in a Stripe account and can be retrieved through the Events API. [4]

Webhook idempotency is a database promise, not a handler hope

Bottom line: Verify the raw Stripe payload first, insert the event ID into durable storage with a uniqueness guarantee, then run invoice, entitlement, email, or receipt side effects only after that insert wins.

Stripe Docs tells webhook builders to verify signatures by passing the event payload, the Stripe-Signature header, and the endpoint secret. [2]

That verification step proves the payload came through the expected signing secret; it does not prove your app has not already handled the event. Duplicate protection needs an application-owned record keyed by the Stripe event ID.

Separate Stripe API idempotency from webhook dedupe

Stripe Docs says idempotency keys on API requests let clients safely retry requests without accidentally performing the same operation twice. [3]

That mechanism belongs to outbound API calls, such as creating a Checkout Session or invoice item. Incoming webhooks need a different guard because the side effect is in your system: marking an invoice paid, granting access, sending a receipt, or writing tax metadata.

Stripe's webhook guide has a dedicated duplicate-event section and tells readers to log processed event IDs, then avoid processing an event that is already logged. [1]

Clean verdict: Treat event.id as the first dedupe key, and put a unique database constraint behind it so two concurrent deliveries cannot both win.

The Node handler should keep the raw body until verification passes

Stripe Docs says signature verification requires the raw request body without changes, and warns that frameworks can edit request bodies by adding or removing whitespace, reordering key-value pairs, or changing encoding. [2]

In Node, that means your webhook route should receive bytes or a raw body string before JSON parsing. Stripe's Node example constructs the event with request.body, the signature header, and the endpoint secret before switching on event.type. [1]

After verification, store a minimal processed-event row: event ID, event type, Stripe object ID, received time, processing status, and a checksum or reference to the raw payload if your retention policy allows it.

Avoid verdict: Do not parse JSON first, send receipts first, or update invoice state before the processed-event insert has committed.

Invoice side effects should be small, replayable, and ordered by source state

Stripe Docs says an Event object describes activity in an account and includes a linked API resource in its data.object field. [4]

For invoice webhooks, keep the first transaction narrow. Insert the event row, record the latest known invoice status, and enqueue customer-visible work such as receipt rendering or entitlement updates. If the handler crashes after the insert, a retry sees the event as known and can continue from an explicit status instead of blindly running every side effect again.

Want this as a drop-in kit? Stripe Invoice Tax Kit packages invoice metadata, tax ID collection, raw-body webhook signature verification, invoice status syncing, and receipt rendering for Node builders.

A fast webhook audit starts with the once-only boundary

Search your webhook route for constructEvent, raw-body middleware, and any database write that runs before signature verification. Stripe Docs says the endpoint secret comes from the webhook endpoint details, and that each endpoint has its own secret. [2]

Then search for every side effect after the switch on event.type. Anything that sends email, grants access, updates invoice state, or emits analytics should be downstream of a durable duplicate check.

Finally, verify your own retry story. Stripe's duplicate-event guidance is explicit about logging processed events, and API idempotency docs cover outbound request retries; use both, but do not confuse one for the other. [1] [3]

This article is informational and is not a substitute for a security, billing, tax, or legal review of your own product.

CI Tripwire has not commissioned independent expert review of this article. Read more about the organization byline at contributors and the source posture at sourcing.

Corrections can be routed through the corrections note. Sources: 4 entries, Stripe documentation, last reviewed 2026-06-09.

Sources

  1. Stripe Docs, Receive Stripe events in your webhook endpoint.
  2. Stripe Docs, Resolve webhook signature verification errors.
  3. Stripe Docs, Idempotent requests.
  4. Stripe Docs, Events.