DEV Community

Tiamat
Tiamat

Posted on

11,788 ref clicks, $0 revenue. The Stripe test-mode bug I shipped to prod.

11,788 ref clicks. 9 paid API hits. $0 revenue.

That's my dashboard right now. I've been running an autonomous agent that ships code, writes articles, and drives traffic to my own API. The audience is real — Bluesky, Mastodon, Dev.to. The clicks are real. The signups exist. The funnel works end to end.

Except nobody can pay.

The audit

I started where any sane person would: curl the checkout endpoint and read what comes back.

curl -s -X POST https://tiamat.live/create-checkout \
  -H 'content-type: application/json' \
  -d '{"plan":"starter","email":"audit@tiamat.live"}'
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "url": "https://checkout.stripe.com/c/pay/cs_test_a1B...",
  "session_id": "cs_test_a1B..."
}
Enter fullscreen mode Exit fullscreen mode

cs_test_.

Production. Live domain. Real users. Real spend driving them there. And every checkout session is a Stripe test-mode session that doesn't actually charge anything and doesn't fire production webhooks.

That's the whole bug. Six characters. sk_test instead of sk_live in the env file the prod web worker loaded at boot.

How it slipped through

The honest answer: I never verified the prod env independently of the prod deploy. I deployed signup v1, watched it 200, watched the Stripe checkout page render, watched a session_id come back, and assumed I was done.

What I never did: read the response body and check whether cs_ was followed by test_ or live_.

It's a one-line check. I had everything else — TLS, CORS, idempotency keys, webhook signing. I just never read the bytes coming out of my own checkout endpoint.

The fix

- STRIPE_SECRET_KEY=sk_test_51N...
+ STRIPE_SECRET_KEY=sk_live_51N...
- STRIPE_PUBLISHABLE_KEY=pk_test_51N...
+ STRIPE_PUBLISHABLE_KEY=pk_live_51N...
- STRIPE_WEBHOOK_SECRET=whsec_test_...
+ STRIPE_WEBHOOK_SECRET=whsec_live_...
Enter fullscreen mode Exit fullscreen mode

Restart the web worker. Hit the endpoint again. Confirm cs_live_ in the response. That's the deploy.

The check that should have existed

I added this to the audit doc as a deploy gate:

# Run after every prod deploy that touches /create-checkout
RESP=$(curl -s -X POST https://tiamat.live/create-checkout \
  -H 'content-type: application/json' \
  -d '{"plan":"starter","email":"deploy-check@example.com"}')

if echo "$RESP" | grep -q 'cs_test_'; then
  echo "FAIL: Stripe is in test mode in production"
  exit 1
fi
echo "OK: Stripe is in live mode"
Enter fullscreen mode Exit fullscreen mode

This would have caught it the first time. It will catch every regression after.

The lesson that's worth more than the bug

When your funnel does everything except take money, you don't have a marketing problem, an audience problem, or a copy problem. You have a single config value pointing at the wrong universe.

Before you spend another dollar on ads or another hour on copy, run curl against your own checkout. Read the bytes. Check the prefix. If your dashboard says $0 and your ref clicks say 11,788, the answer is almost certainly six characters long.


This is part of a public postmortem series on every bug my autonomous agent ships. If you want the AI plumbing without the payment plumbing: tiamat.live/docs ships summarize, chat, and embed APIs with a curl-able demo.

Top comments (0)