Both events are the same payment, one step apart
Underneath, this flow makes four canonical transitions: the attempt’s authorization succeeds, the payment becomes authorized, the capture succeeds, the payment succeeds. Stripe reports all four as two deliveries. How a provider groups transitions into events is a provider fact, not a canonical one, and it is the first thing worth noticing here — the number of webhooks you receive is not the number of things that happened.
Both deliveries carry a PaymentIntent created with
capture_method: manual,
and both carry the same id. Three fields separate them:
status—requires_capture, thensucceeded.amount_capturable— the full amount, then zero.amount_received— zero, then the full amount.
Read only the id out of data.object and the two events are indistinguishable,
which means the first one looks exactly like a completed payment.
The gap between them is not network latency. It is a decision the merchant made on purpose, and it can last days — authorization and capture covers why it exists, where the money is at each step, and what the customer sees.
This is the shape everything else breaks
Every other scenario in the catalog is this run with something done to it. Duplicate delivery sends one of the two events twice. Out-of-order delivery swaps them. A decline stops after the first. The authored definition makes that literal: a scenario is a flow plus a list of perturbations, and this one’s list is empty. How a scenario is built shows the same flow with one perturbation added.
So run this one before you run any of the others, and keep what it sends you. The two payloads are under the timeline below. A duplicate-delivery test that fails against a baseline you have read tells you which field moved; the same test failing against nothing tells you only that something is wrong.
Check that your code sees two facts, not one
The run itself proves very little. Two POSTs go out, your endpoint answers with a success status twice, and the report says so. A handler that discards the body entirely passes.
What the run gives you is the moment to check the thing no verdict here can. Look at your own data afterwards and count the facts you stored:
- the payment was authorized, for an amount, at a time;
- the payment was captured, for an amount, at a time.
If your schema holds one paid boolean and one paid_at, there is nowhere to
put the first fact, so the handler wrote it as the second. That is the mistake
this run exists to surface. It costs nothing while every authorization is
captured seconds later, and it costs an order shipped against a hold the first
time one is not.
The run report says this out loud rather than scoring it. Two of its checkboxes — update application state to reflect the successful payment exactly once, and verify each webhook signature before trusting its payload — are your judgment, not the tool’s.
What the run does not prove
Nothing about your database. Finxture reads the status code your endpoint returns and nothing else. Both automatic assertions in this scenario are about acknowledgment: a 2xx for the authorization event, a 2xx for the payment event. There is no interface through which the tool could check the row.
Not that the order is guaranteed. This scenario delivers the authorization event first and the capture event a second later, every time, because that is what a deterministic sandbox is for. Stripe does not guarantee that events arrive in the order they were generated. Surviving that is a separate problem, and it is not a runnable scenario yet.
Not that your Stripe account produces these payloads. They are generated from a reviewed adapter against one pinned API version. Confirming that a real account agrees needs a real event, which is a different tool’s job.
Nothing about a capture that comes later. Here the capture follows one second after the authorization. Every interesting failure in a two-stage payment happens when it follows days later, or never: a partial capture, a second capture against the same authorization, a hold that expires uncaptured. None of those are runnable today.