How a scenario is built
Underneath a Finxture scenario there is no webhook. There is an event in the real world — a payment was authorized, then captured — and two separate things stacked on it: an adapter that renders that event in one provider's vocabulary, and a decision about how the delivery gets broken.
That event is authored on its own, in a file with no provider word and no calendar date in it. The absence is what makes it a layer rather than a Stripe fixture with the names filed off.
What the rendering layer does
Every payload, and the run-relative time each one is delivered at, is generated from that adapter and served as a file: JSON or YAML. Open one — none of it is hand-written. The same scenario produces the same events on every run, and a change to any payload fails the build.
What breaks, and why it stays portable
Deliver the capture notification twice. Reverse the two events. Drop either one. The flow is untouched and the rendering is untouched; what changes is one array in one file.
That array targets payment-succeeded, a canonical event id — not the Stripe name it is rendered as. Which is why the same perturbation survives a change of provider: an adapter that rendered that one event as three would deliver three, twice, with no edit to the scenario.
Where all of it should land
In every variant a correct integration ends in the same place: the payment is captured and your side agrees. Order changed, count changed, timing changed; the outcome did not. That is the whole reason to keep the layers apart.
What none of this does yet
The convergence just above is the target the scenarios are designed around. It is not a thing the product measures, and there are three gaps worth knowing before you decide the design is worth anything.
There is no reconciliation API. Finxture delivers the events and watches what your endpoint answers. It does not read your database, and there is no interface through which it could. What it verifies automatically is narrow: that each delivery was acknowledged with a 2xx. Whether your payment row ended up in the right state, exactly once, is a manual checkbox on the run report — your judgment, not a verdict the tool can issue.
Only duplicate delivery actually runs. Of the four perturbations above, one is executable today: a zero-interval duplicate. The others fail explicitly rather than being quietly approximated — the delivery planner rejects a perturbation it cannot honour instead of running something adjacent and calling it the scenario. Out-of-order and missing deliveries are explained on this site already, at Out-of-order and late webhook delivery, but neither is a runnable scenario.
One provider, so the canonical layer is unproven as canonical. Everything above rests on the claim that the middle layer is a rendering of a provider-neutral event, and a model with exactly one adapter has never had anything push back on it. The Stripe projection is also narrower than Stripe: it emits PaymentIntent objects, and Charge and PaymentMethod appear only as correlated identifiers rather than as projected objects. The second adapter is the test of the design, and it has not been run.
Test Stripe webhooks on localhost without ngrok runs this same scenario end to end against a local endpoint.