Connected commerce
Make payment effects repeat-safe even when webhook delivery repeats
An endpoint can receive a legitimate event twice, or learn about a later state before an earlier one. The important invariant is that your business action remains correct when delivery is repeated, delayed or interrupted.
Stripe's webhook guide documents duplicate delivery and no guaranteed event order, and requires signature checks against the raw body. It recommends quick acknowledgement and asynchronous handling. This evergreen guidance defines the delivery contract, not a new release. (Stripe)
Stripe's idempotency mechanism for outgoing API requests is a separate facility. It does not automatically make your incoming webhook handler, database transaction or fulfilment provider idempotent. API-version behaviour matters, so follow the request documentation for the endpoint you actually call. (Stripe)
Use three identities for three different jobs
| Identity | Purpose | What it cannot establish alone |
|---|---|---|
| Event delivery ID | Avoid reprocessing the same event record | Whether another event already caused the same business effect |
| Business-effect key | Make one intended action repeat-safe | Whether a distinct refund, adjustment or partial fulfilment should be allowed |
| Outgoing idempotency key | Retry a supported provider API operation consistently | Whether local state and a remote side effect committed together |
For a hypothetical order, marking it paid is one effect. Issuing a refund is another. A key that deduplicates everything by order ID could suppress a legitimate later operation. Define keys around the action and relevant account/object context, with explicit rules for partial payments or multiple charges.
Acknowledge only after you can recover the work
Illustrative architecture, not production-ready SDK code:
verify signature using unmodified body and endpoint secret
persist accepted event in a durable inbox with a unique event key
return success after durable acceptance
worker:
load current business state and required provider evidence
begin transaction
claim the action-specific business-effect key
apply allowed state transition
append any remote action to a durable outbox
commit
outbox worker:
send using the provider's supported retry/idempotency contract
record observed result; reconcile uncertain outcomesThe durable inbox closes the gap between acknowledging receipt and losing an in-memory task. The transaction couples local state with the intent to perform a remote action. It still does not create a distributed transaction with the remote provider: after a timeout, investigate or reconcile the uncertain result before issuing a logically new operation.
Test disagreement, not only the happy path
| Case | What to verify |
|---|---|
| Same event delivered twice | One accepted effect, with repeated delivery visible in the log |
| Different events concern the same paid order | The business transition is applied once where that is the intended rule |
| Events arrive in reverse order | The state machine does not undo a later authoritative state |
| Worker stops after local commit | Pending outbox work resumes rather than disappearing |
| Remote timeout after possible success | Reconciliation determines outcome without inventing a second action |
Avoid deciding order from timestamps alone. When a snapshot is insufficient or evidence is missing, retrieve the appropriate current object through the documented API and retain its relationship to the event. An event log and the current business record serve different purposes.
Keep an exception queue with an owner
A retry limit is not a resolution policy. Give permanently failing or ambiguous events a visible state, a responsible person and a safe replay procedure. Logs should carry identifiers and failure categories rather than full payment or customer payloads everywhere.
Budget for reconciliation and operational support as well as implementation. No exactly-once guarantee, live charge or measured conversion improvement is claimed here. Payment integration and system integration can define those boundaries as part of a connected commerce project.
Sources
- Receive Stripe events in your webhook endpoint — Stripe
- Idempotent requests — Stripe