Faithful to the live Stripe API (test-mode capture 2026-06-09, account version pinned
2024-12-18.acacia). The object shapes, event names, error envelopes, IDs and formats below are
exactly what real Stripe returns — this page is the vocabulary to assert against, not a tutorial
on Stripe. If you know the API, the only new surface is the control layer at
the bottom.
Objects
- payment_intent
- charge
- subscription
- invoice
- customer
- event
requires_payment_method with last_payment_error set, and stays retryable.Events
resource.action. data.object is a full snapshot sharing the REST serializer. Three origins
drive them: your own API calls, the clock (renewals, auto-finalize, dunning), and the world
(the issuing bank declines, demands 3DS, the cardholder disputes). 30 types captured live:
- Era layering is real: a recurring price dual-writes a legacy
plan.created; paying an invoice emitsinvoice.paidand deprecated-but-aliveinvoice.payment_succeededand new-generationinvoice_payment.paid. Handlers in the wild key on any of the three. event.request.idis not a reliable did-I-cause-this marker — internally generated children of your own call (the subscription-create invoice) carryrequest: { id: null }.invoice.upcominghas no persisted object (data.object.idisnull) — it fires before the renewal invoice exists.- Delivery:
Stripe-Signature: t=<ts>,v1=<hmac>— HMAC-SHA256 over"{t}.{raw_body}"keyed by the endpoint’swhsec_. Stripe returns that secret only on create; in the sandbox it’s stable and re-readable (test a webhook).
Errors
One envelope, but two surfaces — anderror.code is per-class optional (whole families ship
message-only: both 401s, bad Stripe-Version, “Invoice is already paid”).
1 · A call fails → HTTP error response.
Strictness boundary: names and enums are strict 400s; numeric bounds are silently clamped
(
limit=99999 → 200).
2 · A payment fails → the error is also durable state. The 402 body carries the issuer’s
verdict and the full PaymentIntent, post-bounce:
failed charge with failure_code + outcome, the PI’s
last_payment_error, plus payment_intent.payment_failed and charge.failed events. Async
failures (ACH returns, disputes) skip the synchronous envelope entirely — state + events are the
only surface.
Magic values
Stripe is the one API whose documented test contract is magic inputs — so the pack honors them. Each documented test card is an alias for the matching world trigger: your existing suite keeps passing after the repoint, unmodified.
Each recognized value desugars to the same apply-fn as the matching control-surface call —
same state, same events, no separate code path. A documented Stripe test value the pack doesn’t
recognize yet fails loudly (
unrecognized Stripe test value) instead of silently succeeding —
a repointed suite never passes for the wrong reason.
For outcomes the real test mode can’t produce on demand (a decline at renewal, a dispute on a
seeded charge, a webhook retry storm), use the control surface — that’s the point of the sandbox.
Test clocks
/v1/test_helpers/test_clocks is fully supported — create, attach customers (hidden from the
plain customer list, exactly like real Stripe), advance, delete. Two deliberate upgrades over the
real thing:
A Stripe test clock here is a named handle on the world’s single clock — one timeline per
world. Need two independent timelines? Run two worlds (worlds); that’s the
CI model anyway. The only observable divergence: a suite can never catch the
advancing
status — if yours asserts it, it’s testing Stripe’s infrastructure, not your code.
Pinnable values
The fields that originate outside Stripe’s logic — the only ones you supply. Everything else (ids,status, totals, timestamps, number) is engine-derived and unfakeable.
Formats & conventions
Control (prod-break)
The only surface that isn’t real Stripe. Everything above is the vendor’s; this is how you make a given outcome happen on demand.- Event names and behavior are pack-declared — the engine is generic; the Stripe vocabulary
above lives in this pack.
sandbox.world.next()lists the world events whose preconditions hold. - Control calls hit
/__admin__/*. Their errors go to your test, not the app under test, and are not Stripe’s error envelope. - Magic test inputs are honored here (unlike other packs) because they’re Stripe’s documented
contract — each desugars to the same apply-fn as its
world.triggerequivalent. They only cover call-time outcomes; renewal-time and webhook-time outcomes need the control surface. - Concepts: exogenous values · force a branch · world events · the clock · HTTP API.