stripe-mock is Stripe's official mock HTTP server, and it's genuinely good at its stated job: it knows every Stripe API URL, validates your request parameters against the real API's JSON Schema, and returns spec-generated sample responses โ fast, offline, in your test suite. If that's all you need, install it and move on.
But its README is refreshingly blunt about what it doesn't do, by design:
This guide is exactly that: define your own payments mock โ hosted on a public HTTPS URL (so CI, teammates, mobile builds, and deployed previews can all hit it), stateful (POST a payment, GET it back, PATCH it settled), with declines, timeouts, and exact retry sequences on demand, plus HMAC-signed webhooks. No account, no keys, no binary to run.
curl -X POST https://mockbird.mockbird.workers.dev/api/projects -H 'content-type: application/json' -d '{"preset":"payments"}') creates a ready payments sandbox: charges/refunds/subscriptions/customers CRUD plus a Stripe-shaped POST /v1/payment_intents → /v1/payment_intents/:id/confirm flow and /v1/balance. The paste below builds a custom-shaped mock instead โ use it when you want finer control over the Stripe shapes.BASE=https://mockbird.mockbird.workers.dev
P=$(curl -s -X POST $BASE/api/projects -H 'content-type: application/json' \
-d '{"name":"payments-mock","blank":true}')
PID=$(echo $P | sed 's/.*"id": *"\([^"]*\)".*/\1/')
KEY=$(echo $P | sed 's/.*"adminKey": *"\([^"]*\)".*/\1/')
curl -s -X POST $BASE/api/projects/$PID/resources -H "x-admin-key: $KEY" \
-H 'content-type: application/json' -d '{
"name":"payment_intents",
"fields":[
{"name":"amount","type":"number"},
{"name":"currency","type":"oneOf","values":["usd","eur","gbp"]},
{"name":"status","type":"oneOf","values":["requires_payment_method","processing","succeeded","canceled"]},
{"name":"customerEmail","type":"email"},
{"name":"created","type":"pastDate"}
],
"seed":5
}'
You now have GET/POST/PATCH/DELETE /m/<PID>/payment_intents with 5 seeded records:
curl "https://mockbird.mockbird.workers.dev/m/<PID>/payment_intents?limit=1"
[
{
"id": 1,
"amount": 91,
"currency": "gbp",
"status": "canceled",
"customerEmail": "ezra.nielsen37@example.com",
"created": "2026-07-23T09:12:02.663Z"
}
]
The field set is yours โ add clientSecret (uuid), cardLast4, captureMethod (oneOf), whatever your app reads. Filters work out of the box: ?status=processing, ?amount_gte=1000.
This is the exact thing stripe-mock's README says it will never do โ and it's usually the first thing a checkout test needs:
# your checkout creates an intent
curl -s -X POST $BASE/m/$PID/payment_intents -H 'content-type: application/json' \
-d '{"amount":2000,"currency":"usd","status":"processing","customerEmail":"jo@example.com"}'
# โ {"amount":2000,"currency":"usd","status":"processing","customerEmail":"jo@example.com","id":6}
# your UI polls โ the record is really there
curl -s $BASE/m/$PID/payment_intents/6 # status: "processing"
# the test driver plays the gateway: settle it
curl -s -X PATCH $BASE/m/$PID/payment_intents/6 -H 'content-type: application/json' \
-d '{"status":"succeeded"}'
Every write is visible to every later read, from any client โ your browser, your CI job, a teammate's curl. That's a database under the mock, not a fixture file.
stripe-mock returns a success fixture even for requests that should fail. Here, any request fails exactly when you tell it to:
curl -si -X POST "$BASE/m/$PID/payment_intents?mock_status=402" \
-H 'content-type: application/json' -d '{"amount":100,"currency":"usd","status":"processing"}'
# HTTP/2 402
# {"error": "simulated 402 error (mock_status)"}
# and no phantom record is created โ the write is not applied
Any status 400โ599 works (mock_status=429 for rate-limit UX, 503 for gateway-down banners), and mock_delay=4000 composes with it for slow-failure paths. Full recipes in testing loading & error states.
Card-decline retry logic is miserable to test against anything real. mock_seq scripts the exact sequence of outcomes:
# first attempt 402, second 402, third succeeds โ exactly
curl -s -o /dev/null -w '%{http_code}\n' -X POST \
"$BASE/m/$PID/payment_intents?mock_seq=402,402,201&mock_seq_key=card-retry-9" \
-H 'content-type: application/json' \
-d '{"amount":20,"currency":"eur","status":"processing"}'
# run it 3x: 402, 402, 201
Each mock_seq_key tracks its own position, so parallel tests don't trample each other's sequences.
stripe listen, no tunnelPoint the project's webhook at your handler and every create/update/delete fires a signed POST, with a familiar-looking secret:
curl -s -X PUT $BASE/api/projects/$PID/webhook -H "x-admin-key: $KEY" \
-H 'content-type: application/json' -d '{"url":"https://your-app.example.com/hooks/payments"}'
# โ {"ok":true,"webhook":{"secret":"whsec_a60818b5d376โฆ","events":["created","updated","deleted"]}}
Deliveries carry X-Mockbird-Signature: sha256=โฆ (HMAC-SHA256 of the raw body) so your verification code path actually runs. Verify snippet and delivery-log details in sending test webhooks; a full payment-webhook walkthrough is in the mock payment API guide.
| You wanted | stripe-mock | Mockbird |
|---|---|---|
| POST data visible on later GETs | โ stateless by design | โ persisted (real CRUD) |
| Simulate declines / specific errors | โ success fixtures only | ?mock_status=402 (any 400โ599) |
| Scripted outcome sequences | โ | ?mock_seq=402,402,201 |
| Slow-gateway latency | โ | ?mock_delay=3000 (+ jitter, chaos) |
| Webhook deliveries | โ | โ HMAC-signed, whsec_ secret |
| Reachable by CI / teammates / previews | your machine or network | โ public HTTPS URL |
| Setup | brew / Docker / Go binary | one curl, no install |
| Exact Stripe URLs + param validation | โ generated from the real spec | โ your own shapes |
The clean split: stripe-mock for sanity-checking calls against Stripe's schema, Stripe test mode for integration truth, Mockbird when the thing under test is your flow โ checkout states, decline UX, retry logic, webhook verification, order screens โ and you want a stateful backend that any client can reach without a payments account. They compose; plenty of teams use all three.
/v1/payment_intents with templated bodies? Custom routes do that โ same technique as the OpenAI mock guide. To replay a recorded real API instead, see mocking third-party APIs.The paste block in section 1 is the whole setup โ or open the dashboard and click it together. The same project also serves GraphQL, exports OpenAPI + TypeScript types, and ejects to db.json anytime โ no lock-in.