Checkout succeeds; the order never happens

Customers pay. The app never sends the email, creates the account, or unlocks the thing they paid for.

Why it happens

Fulfilment runs off the webhook — checkout.session.completed, or payment_intent.succeeded — and the app either does not listen for that event, listens on a route that is not deployed, or handles it and throws before writing anything.

How to tell

  • Deliveries answered 200 but nothing written; or answered 500 at the time of each payment.
  • The endpoint's event list does not include the completed event.
  • An error in the handler for every payment, grouped under one fingerprint.

The fix

  1. Subscribe the endpoint to checkout.session.completed, and to payment_intent.succeeded if you use intents directly.
  2. Make the handler idempotent: record the Stripe event id, do the work, answer 200; answer 200 again for a duplicate.
  3. Replay the missed events from Stripe for the window.

The prompt for your builder

Paste this into Lovable, Bolt, Cursor, Claude Code or whatever built the app. Replace anything in capitals. It never asks you to paste a secret into a chat; keys go into your host’s environment, by you.

Prompt for your builder
Make my Stripe webhook handler fulfil orders on checkout.session.completed: find the order by the session's client_reference_id or metadata, mark it paid, send the confirmation, and store the Stripe event id so a replayed event is ignored. Return 200 only after the database write succeeds, and 500 with a logged reason if it fails so Stripe retries.

How Keelnest catches it

The checkout journey runs after every publish and stops before paying. Revenue Guard reconciles Stripe's events against your own hours, so paid-but-unfulfilled shows as payments flowing while the journey's last step fails — two facts that only make sense together.

Know the moment this happens.

Keelnest watches your production app every five minutes and tells you in plain English what broke — with the fix. Free for your first app.

Related