The first line of a payment go-live checklist, on Stripe, Adyen, Chargebee or RevenueCat, is a question: which keys does production load? Stripe’s own docs name the failure: “You can’t process live payments if your integration is still using your test API keys.” Every item below is in the order it happens, with a check and the evidence to keep.

This is the payments step of launching, where SaaS billing process best practices meet real money for the first time. Everything else about launch day, from deploys to support, sits on the wider go live checklist.

The payment go-live checklist: what has to be true before the first real customer pays

A payment go-live checklist for any provider is a short list of conditions: production loads live keys and every other environment loads test keys, the live webhook endpoint is registered with its own signing secret, live products and prices exist, the statement descriptor is set, and the failed-payment path is in place before the first real customer pays.

Each condition below has a check you can run yourself and a piece of evidence worth saving with a date on it. The checks are my method; the key prefixes and the per-endpoint secret come from Stripe’s keys and webhooks pages.

ConditionHow to check itEvidence to keep
Production loads live keys; every other environment loads test keys, from separate variablesRead the prefix of the key each environment actually loaded: Stripe live keys start pk_live_, rk_live_ or sk_live_, sandbox keys pk_test_, rk_test_ or sk_test_The variable list for each environment, dated
A live webhook endpoint exists, with a signing secret of its ownIn live mode, open the endpoint and confirm production holds that endpoint’s whsec_ secret; Stripe gives the same URL a different secret for test and liveThe endpoint URL and the name of the variable production reads, dated
Live products and prices existEvery product and price id production reads resolves in live mode, since objects in one mode aren’t accessible to the otherA list pairing each test id with its live id
The statement descriptor is setRead it back in the Dashboard where it was setThe descriptor text and the date you set it
The failed-payment path existsMake a test subscription payment fail and follow what the app tells the customer and what it does to their access (how to handle failed subscription payments)The event ids from that run
The money-path conditions holdWork through before you turn on payments and six ways a vibe-coded checkout leaks money, test mode keys in production includedA note per condition: holds, or what is left

To separate Stripe test and live keys, give each environment its own variables and never one variable whose value you swap before a deploy. Then read, in each environment, which prefix it loaded: a startup log line that prints only the prefix, never the key, is enough. That reading is how you verify production uses live payment credentials. A variable called STRIPE_LIVE_KEY proves nothing until the running app reports a live prefix. Stripe adds a second check from its side: each key’s overflow menu opens its own request logs, so after the deploy the live key’s log should show production’s requests arriving. Listing every environment and what each one loads is a job of its own: separate variables, databases and keys.

For the webhook row, the Stripe steps for registering and verifying the live destination, and the full test-to-live object map behind the products row, are in Stripe test mode to live. Checking the signature on every delivery is webhooks security. All of this sits on top of the integration itself, and building that is how to integrate Stripe.

A subscription billing launch checklist adds the plans, the subscription lifecycle and dunning on top of those six rows; the lifecycle gets its own rehearsal seven days out. For production checkout testing on Stripe, the check is the launch-day watch below, not a test charge on a real card.

In the Production Hardening Sprint, this is deliverable 5.5, Separated payment environments: we verify that test and live credentials are isolated by environment, and we check it by inspecting the provider accounts and running isolated test-mode transactions.

The statement descriptor: the name customers see on the card statement

Stripe shows your name to customers through the statement descriptor, on card statements and email receipts; the Dashboard’s account name is for internal use only. Stripe says clear and accurate descriptors can reduce chargebacks and disputes, so set yours before launch, within its length and character rules, and read it back on the first genuine charge.

The rules are in Stripe’s statement descriptor docs. A complete descriptor uses only Latin characters, runs between 5 and 22 characters, contains at least one letter, leaves out <, >, \, ', " and *, is more than a single common term or common website URL, and reflects your Doing Business As name. For card payments you can add a dynamic suffix per charge to a static prefix of 2 to 10 characters, and the whole thing, prefix, *, space and suffix, stays within 22 characters.

Subscriptions behave differently. Stripe creates those charges itself, so you can’t set a descriptor on their PaymentIntent; you set it on the Product or the Invoice instead. And keep expectations modest on the first statement: Stripe notes that most banks show the descriptor consistently, but some might display it incorrectly or not at all.

Seven days out

A week before launch, everything still runs on test keys. The point of this week is a full rehearsal, with each step leaving something you can show later. The steps follow Stripe’s testing and webhooks docs; the evidence each one keeps is my working rule.

  1. 01 Run the full checkout with Stripe integration test cards: 4242424242424242 for a successful Visa payment, 4000000000000002 for a generic decline, 4000002760003184 for a card that always asks for authentication. Keep the three payment ids.
  2. 02 Pick a real test event from your own checkout run, resend it from the Stripe Dashboard, and confirm the app made one business change, not two. Keep the event id and the database row.
  3. 03 Walk one test subscription from signup to cancellation and confirm access follows each change. Keep the event ids.
  4. 04 Refund a test payment and confirm the app handles the refund the way your refund policy says. Keep the refund id.
  5. 05 Search the variables of every non-production environment for a live prefix and confirm none holds one. Keep the dated list.
  6. 06 List every object production will need a live id for: products, prices, the webhook endpoint. One day out becomes copying, not remembering.

Resend from Stripe rather than posting a saved request again. “Stripe generates the timestamp and signature each time we send an event to your endpoint”, and Stripe’s libraries have a “default tolerance of 5 minutes”, so a captured request replayed more than five minutes later fails the signature check and tells you nothing about the handler. The Dashboard’s Resend works for up to 15 days after the event was created. Why one event must produce one change, and how to build that, is how to make a webhook handler idempotent; the states a test subscription passes through are what is the subscription lifecycle in Stripe.

For a Stripe staging environment, use a sandbox, which Stripe defines as “an isolated test environment”, and keep local development and CI in sandboxes of their own so automated tests don’t affect your settings or data. Whether staging should ever hold live keys is the question “Should staging use live Stripe keys?” in the Stripe test-to-live article’s FAQ.

Adyen and Chargebee rehearse in their own test places. On Adyen it is the test Customer Area, and Adyen does not automatically copy those settings to the live environment, so the rehearsal also produces your list of settings to recreate. On Chargebee it is the Test site, where Time Machine, which Chargebee says lets you “virtually travel back in time”, helps you anticipate how your billing rules will perform.

One day out

The day before is for creating live things and putting each one where only production can read it.

  1. 01 Create the live products and prices and write each live id next to its test id.
  2. 02 Register the live webhook endpoint at an HTTPS URL and put its signing secret in production's variables only.
  3. 03 Set the live keys in production, then confirm every other environment still holds test keys.
  4. 04 Rotate any secret or restricted key that ever sat in a repository, and once production runs on the new key, expire the old one now instead of waiting out the grace period.
  5. 05 Set the statement descriptor and read it back.

On Stripe, an HTTPS URL for the webhook endpoint is “required in live mode”. The third step matters most, because a misconfigured environment can process real charges during testing or accept no real payments in production. When someone says their app charged a real card in test mode, or that a test signup charged a real card, my reading is that a live key sat in an environment everyone believed was testing.

The rotation step has a Stripe detail worth knowing. When you rotate a key in the Dashboard, “both the old and new keys work for up to 7 days”, and “If you choose Now, the old key is deleted”. For a key that ever reached git, choose Now as soon as production runs on the replacement; the safe sequence is how to rotate API keys safely. In 21 third-party apps I audited, three had a real secret permanently in git history, and one of those secrets was a Stripe test key with its webhook secret. I audited those 21 apps in June and July 2026, and they are a set I selected, not a random sample, so the count shows the mistake happens, not how often across all AI-built apps.

Picture how the third step goes wrong. A founder copies production’s environment variables into staging to debug a checkout problem. Staging now loads the live key, so the next test signup charges a real card: the first of the two failures named above. Reading which key prefix each environment loaded would have caught it before that signup. The lesson I take from it: which key an environment loads decides which money is real, so check the key it loaded, not the name of the variable.

The day

Launch day on Stripe is a switch and a watch, not a test charge, because “The Stripe Services Agreement prohibits testing in live mode using real payment method details.” Switch production to live keys, confirm the live webhook endpoint, then follow the first genuine payment through its charge, its webhook delivery and the change to the customer’s account.

  1. 01 Switch production to the live keys, redeploy, and read back the prefix production loaded.
  2. 02 Confirm the live webhook endpoint is registered in live mode, subscribed to the events the app handles, and not disabled.
  3. 03 Watch the first genuine payment from the charge to the webhook delivery to the change in the customer's account, and read the statement descriptor on it.
  4. 04 Record all four with timestamps: the prefix, the endpoint, the payment id and the delivery.

That line is on Stripe’s testing page, and it means the first live payment you watch on Stripe is a customer’s, not one you make yourself to try the checkout. The Event deliveries tab for the endpoint shows whether that payment’s events were Delivered, Pending or Failed. Whether the first real payment actually worked, end to end, is the last question in the before-you-turn-on-payments FAQ.

Adyen asks for something different. Its End-to-end testing section says to “test possible scenarios with real payment details”: a successful payment with real details for each payment method you offer, a refused payment, a payment refused for fraud, and a refund and a partial refund. That is Adyen’s rule for an Adyen account; it does not carry over to Stripe.

The first week

Read the webhook delivery log every day. In live mode Stripe retries a failed delivery for up to three days, so a handler that fails on the first day can keep failing through every retry unless someone reads the log. What to check when events fail or the database stops updating is Stripe webhooks failing.

Once during the week, compare the provider’s customer list with your own database, row by row for the paying accounts: keep Stripe subscriptions in sync with the database. Then open the customer portal as a customer would, with a real account, and check what it lets them change: how to set up Stripe customer portal.

Keep the billing operations runbook checklist open all week. The first refund request, failed renewal or card dispute is easier to handle when the steps are already written down.

What to do if something fails

If live keys turn up in a test build, take them out, rotate the live key with the old one expired now (the grace period works as described one day out), and refund any real charge the test build took.

A live app still on test keys takes no real money. Switch production to the live keys and repeat the day’s four checks from the top.

A webhook that never arrives gets three checks: the endpoint URL, the signing secret and the event list the endpoint is subscribed to. The Stripe webhooks failing article sets out which check to run first and what each one rules out.

A live payment that fails after test mode worked can be a wiring error or a real issuer decision, and the Stripe test-to-live article’s FAQ “Why did my live payment fail when test mode worked?” separates the two.

Who to call: the provider’s support for anything about the account’s state, such as a held payout or a verification request, and the runbook for everything else.

Mobile subscriptions: the same checklist through RevenueCat or the app stores

The conditions in the first table do not change with the provider. Where the test and live switch lives does.

ProviderWhere the test and live switch livesThe provider’s own go-live document
StripeSeparate sandbox and live mode keys (_test_ and _live_ prefixes); a sandbox product or price can’t be used in a live paymentStripe’s go-live checklist, linked from its API keys page; the Stripe test-to-live article on this blog
AdyenA test Customer Area and a live Customer Area; settings are not copied across automatically; a live API key and live endpointsThe Adyen go-live checklist for online payments: Account, Finance, Risk and compliance, API communication, Webhooks, End-to-end testing
ChargebeeA Test site and a Live site, with distinct API keys; activating the Live site, where you pick your Chargebee plan, is a separate stepThe Chargebee go-live checklist: Transfer Configurations, Additional Settings, Testing, External Settings, Post-Go-Live Optimization
RevenueCat and the app storesA Test Store API key during development, replaced by the platform-specific API key (iOS for App Store, Android for Google Play) for release builds; purchases tested in Apple Sandbox and Google testingThe RevenueCat checklist for app subscription launches: plan limits, user identity, purchases, webhooks and integrations, release

Adyen’s go-live checklist opens with “Follow this checklist before you start accepting live payments” and has you generate an API key in the live Customer Area for your live integration.

Chargebee’s go-live checklist starts after the Test site is configured and says plainly that “Plan selection and checkout are a separate step from verifying go-live settings.” It also recommends “a few small-value test transactions with real cards in your Live site before offering it to your customers”. That is Chargebee’s advice; if your Live site charges through Stripe, I would check it against the launch-day Stripe rule quoted above before running it.

RevenueCat’s launch checklist puts one warning above every item: “You MUST replace your Test Store API key with the correct platform-specific API key (iOS, Android, etc.) before submitting your app for review.” Its reason is blunt: “Using a Test Store API key in production will crash your app.” Before launch it has you test purchases with that platform key against real store products in the store sandboxes, and it notes that sandbox subscriptions auto-renew at an accelerated rate.

Where the sprint fits

Deliverable 5.5, named under the first checklist on this page, is one of 123 in the Production Hardening Sprint. Its result goes into deliverable 13.1, the production readiness report, which we verify by accounting for all 123 IDs, keeping failures visible until resolved and explaining genuine non-applicable items. The sprint covers one codebase. Every deliverable, with how we verify it, is in the published scope.

Common questions about taking payments live

How to test Stripe test mode?

Use your test API keys in a sandbox and pay with Stripe’s test cards: 4242424242424242 succeeds, 4000000000000002 returns a generic decline, and 4000002760003184 always asks for authentication. Then resend one test event from the Dashboard to your webhook endpoint and refund one test payment, so the webhook and refund paths run too.

What is a Stripe test mode card and what does it do?

A Stripe test card is a card number you use with test API keys to simulate an outcome, such as a successful payment, a decline, a dispute or a 3D Secure authentication, without moving money. Stripe calls them “fake” credit cards, and the transactions they create in a sandbox don’t move funds.

Yes, as far as Stripe’s own documentation goes: Stripe publishes its test card numbers for use in sandboxes, where test transactions don’t move funds. What its testing page says the Stripe Services Agreement prohibits is the opposite, testing in live mode using real payment method details. That is Stripe’s rule, and I am not giving legal advice beyond it.

Is RevenueCat only for mobile apps?

No. RevenueCat’s web documentation describes RevenueCat Web as “the umbrella for selling subscriptions and other purchases on the web while unlocking the same RevenueCat entitlements you use on mobile”, with three billing engines: RevenueCat Billing, Stripe Billing and Paddle Billing. Its launch checklist, though, is written for iOS and Android.