← Learn

Stripe webhook returned 200, but paid access still failed

A webhook can succeed at the HTTP layer while your app never grants the user paid access. Shipshape checks the resulting state, not just delivery.

A common SaaS launch bug is quiet: Stripe sends a webhook, your endpoint returns 200 OK, Stripe stops retrying, and the user still does not get paid access. The transport worked, but the business state failed.

Why 200 OK is not enough

Webhook tools can forward, replay, and log events. That helps delivery. It does not prove that your app updated the right user, subscription row, entitlement flag, or organization membership before returning success.

The state assertion pattern

A launch-state test sends a safe local/staging event, then checks the app's resulting state: for example, checkout.session.completed should produce subscription.status = active, or user.created should create a profile row.

How Shipshape handles it

In shipshape.yml, define the webhook URL and expected state endpoint. Shipshape triggers the test event only when you explicitly pass --run-assertions, then marks the Paid State Gate as pass, fail, incomplete, or not configured.

→ See the assertion CLI flow

FAQ

Why can a Stripe webhook return 200 but still be broken?
The handler may acknowledge the event before updating durable app state, or update the wrong user/row. Stripe sees success, but your app state is wrong.

Does Shipshape replace Stripe CLI or Hookdeck?
No. Those help delivery and replay. Shipshape checks the resulting app state after the event.

Related questions

Check your own app
Free passive scan, ~10 seconds, no login.