# Moving a Lemon Squeezy integration from test mode to live mode

> Test and live are separate worlds with separate keys, webhooks and variant ids. The checklist, and the silent mismatch that sells nothing.

You launch. A customer pays. The money shows in the dashboard. Their account
never unlocks, and nothing is red.

With Lemon Squeezy the cause is almost always one value still pointing at test
mode, or one thing that exists in test mode and was never created in live mode.

## What is separate

| Thing | Test and live share it? |
|---|---|
| Store and store id | Shared |
| API keys | **No.** A key created in test mode only sees test data |
| Webhooks and signing secrets | **No.** Test webhooks fire only for test data |
| Products and variants | **No.** "Copy to live mode" makes new variant ids |
| Orders, subscriptions, customers | **No** |

The store id is the one value that stays the same, which is why it is the one
people forget to check the others against.

## The failure modes, loudest first

1. **Live key, test variant ids.** Checkout creation fails because the variant
   does not exist in live mode. Loud.
2. **Test key, live settings.** Every checkout is a test checkout. Nobody is
   charged, and nobody notices until the first payout is empty.
3. **Everything live except the webhook.** The live webhook was never created,
   or points at a preview URL, or has the test secret. Payments succeed,
   nothing lands, every page still shows the free plan. Silent. This is the
   one.
4. **Live webhook missing an event.** Deliveries are all green. The events you
   did not tick never arrive, so cancellations or refunds never reach your
   database. Silent for weeks.

## Make the mismatch impossible to miss

- **Keep a mode flag with test as the default.** An env var such as
  `LEMONSQUEEZY_MODE=test`. A fresh clone cannot take real money.
- **Check the key's real mode.** `GET /v1/users/me` (`getAuthenticatedUser`)
  returns `meta.test_mode` for the key. Compare it with your flag in a verify
  script and fail loudly when they differ.
- **Pass the mode to checkout.** `createCheckout(store, variant, { testMode })`
  keeps a mismatched deploy from charging a real card in the wrong world.
- **Drop webhooks from the other mode.** Every payload has `meta.test_mode`.
  If it disagrees with your flag, write nothing. A test purchase must never
  entitle anyone on production.
- **Keep variant ids in env, never in code.** One env var per price
  (`BILLING_PRICE_PRO_MONTHLY`). Test and live get different values from the
  same build.
- **Check every id against the catalogue.** A script that fetches each variant
  (with its price model and product) and compares kind, amount, currency,
  interval, trial, store and `test_mode` turns a pasted test id on production
  into a failed deploy check instead of a failed checkout.

## Launch-day checklist

1. Store activated (identity and payout details approved). Live checkouts are
   refused until then.
2. Products copied to live mode, published, with the right tax category.
3. Test mode switched off. Live API key created. The test key stays in local
   `.env.local` only.
4. Live webhook created at `https://<your-domain>/api/webhooks/lemonsqueezy`
   with a **new** signing secret and every event your handler lists.
5. Production env: API key, webhook secret, store id, mode `live`, and the
   live variant id for every price.
6. Run your verify script against production's env. It should report live
   mode and every price matching.
7. Deploy. Buy your own product with a real card. Confirm the plan shows up.
   Refund yourself. Confirm it goes away.
8. Subscribe, then cancel in the portal. Confirm the app shows the end date.

Step 8 is the one people skip, and it is the only step that proves the live
webhook receives more than `order_created`.

## After launch

Keep test mode working. Point a staging deployment at the test key, the test
webhook and the test variant ids. When you add an event or a price, do it in
test mode first, then repeat it in live mode the same day. Two lists that
must match drift the first time someone updates only one.

---

Agentic Boilerplate: A Next.js repo your agent already knows. Free during launch, then $99 once.

- Site map for agents: https://agenticboilerplate.com/llms.txt
- Public API: https://agenticboilerplate.com/openapi.json
- Contact: agenticstudio@gmail.com
