Test mode, which Stripe now calls a sandbox, shares nothing your webhook depends on with live mode. Each has its own webhook endpoints, signing secrets, API keys, products and prices. Live mode adds what a sandbox does not have: HTTPS with a valid certificate, banks that ask for authentication, payments that take days. And the live endpoint, created later, may send events in a newer API version than the one your code was written against.
What changes when you go live
| In live mode | What breaks | What to do |
|---|---|---|
| Endpoints are separate | No event reaches your app: test endpoints never receive live events | Create the endpoint again in live mode, with the same events selected |
| Each endpoint has its own secret | No signatures found matching the expected signature for payload on every event | Give production the live endpoint's whsec_ |
Keys are sk_live_ and rk_live_ | API calls from your handler fail: the objects of a live event do not exist for a test key | Use the live secret key on the server |
| Products and prices are separate | A checkout or a plan lookup by a test price id finds nothing | Create them in live mode and read their ids from configuration |
| HTTPS is required | Every delivery fails with a connection or TLS error | Serve the endpoint over HTTPS with a valid certificate and TLS 1.2 or newer |
| Banks ask for authentication | A subscription starts incomplete, and a handler that expects active at once never grants access | Grant on invoice.paid, which comes after authentication |
| Some payments are not instant | The checkout completes, the money arrives days later | Also handle checkout.session.async_payment_succeeded |
| The endpoint has its own API version | Fields your code reads come back empty | Pin the endpoint's API version, or read both shapes |
The endpoint and its secret
Stripe's go-live checklist says it plainly: an account has both test and live endpoints, so define live endpoints, and make sure each behaves like the test one. In the Dashboard, switch to live mode first, then open Workbench and Webhooks: the live endpoints and their event deliveries are listed there, apart from the sandbox's.
A new endpoint gets a new signing secret, and the most common live failure is a production server still reading the test one, or the one stripe listen printed. The CLI forwards sandbox events unless you pass --live, and its secret belongs to it alone. Every Stripe signature message, with its causes, is on our page about Stripe's signature errors.
Check also that the live endpoint listens to the same events. An endpoint that misses invoice.paid delivers everything else and still leaves renewals unpaid in your app.
Keys, products and prices
Objects created in a sandbox, such as products, prices and coupons, are not usable in live mode, so a price id copied from test mode means nothing there. Read price ids from configuration per environment rather than from the code, and use the live secret key on the server: a test key cannot read the customer or the subscription of a live event. Every event carries a livemode field; log it next to the event id, and you see at once which mode an event came from.
HTTPS
Stripe requires an HTTPS endpoint in live mode and checks the connection before it sends anything: the certificate chain must be valid, and TLS must be version 1.2 or newer. A test endpoint on plain HTTP, or with an incomplete certificate chain, can work in a sandbox and fail every live delivery with a TLS error or an unable-to-connect error.
Real cards: authentication and delayed payments
The test card 4242 4242 4242 4242 never asks for authentication. A real card may, and in a checkout built on the Subscriptions API the subscription is then created incomplete, invoice.payment_action_required arrives, and only after the customer authenticates do invoice.paid and the move to active follow. A handler that grants access on customer.subscription.created only when it says active leaves those customers locked out. Stripe's test card 4000 0025 0000 3155 asks for authentication, so you can replay this in a sandbox.
Bank debits and other methods that are not instant complete the checkout before the money arrives. Stripe's fulfillment guide checks payment_status on checkout.session.completed and fulfills the rest on checkout.session.async_payment_succeeded. Which event to trust, provider by provider: payment succeeded, subscription not active.
A newer API version
An event is rendered in the API version of its endpoint, or of your account if the endpoint has none, and an endpoint created months after the test one may use a newer version. Some versions move fields. Since 2025-03-31.basil, an invoice no longer has subscription: the id is in parent.subscription_details.subscription. And a subscription no longer has current_period_end: each item has its own. Code written against the older shape reads undefined, finds no subscription and unlocks nobody, without a single error. Pin the endpoint's API version when you create it, or read both shapes:
// invoice.paid: the subscription id, in API versions before and after 2025-03-31.basil
const subscriptionId = invoice.parent?.subscription_details?.subscription ?? invoice.subscription ?? null;
// customer.subscription.*: the end of the paid period, on the item since 2025-03-31.basil
const periodEnd = subscription.items?.data?.[0]?.current_period_end ?? subscription.current_period_end ?? null;
Stripe's Go and .NET libraries go further and refuse an event from another release of the API than their own, with received event with API version …, even when the signature is fine.
When it is fixed
Live mode retries a failed delivery for up to three days with exponential backoff, so once the endpoint answers again, most of what it missed arrives on its own. For the rest, Resend in the Dashboard works for 15 days after the event and stripe events resend for 30. Skip event ids you have already handled: retries and resends deliver the same event again.
How PayHook reports it
PayHook, the webhook inspector we are building, shows whether each Stripe event came from test or live mode next to its signature verdict, and when a signature does not match, its reason reminds you that stripe listen, each Dashboard endpoint and test and live mode have their own secrets. PayHook is in closed beta; the home page has the details.
Sources
- Stripe documentation, Go-live checklist, Sandboxes, API keys, stripe listen, Receive Stripe events in your webhook endpoint and Manage webhook endpoints, checked on 9 October 2026.
- Stripe documentation, Using webhooks with subscriptions and Fulfill orders, checked on 9 October 2026.
- Stripe's changelog for
2025-03-31.basil: the parent field of invoicing objects and item-level billing periods, checked on 9 October 2026; the shapes in API version2026-08-26.dahlia: PayHook's own sandbox capture, 8 October 2026.