Going live
Your integration works in a sandbox. Taking it to production is five steps, in order: finish account verification, create a live key, confirm the account is ready to charge, re-create your configuration in live mode, then cut over and verify with one real payment.
Your code does not change. Flint has one hostname and one API for both modes: same base URL, same request shapes, same webhook signature scheme. What changes is the key you send, the account checks behind real money, and the small set of configuration that exists per mode.
What changes in live mode#
| Test mode | Live mode | |
|---|---|---|
| Key | flint_test_..., bound to a sandbox | flint_live_..., never sandbox-bound |
| Data | Isolated test data | Your real merchant data |
| Cards | Test cards only | Real cards only; test cards decline |
| Money | Simulated; nothing moves | Real charges, refunds, and payouts |
| Webhooks | Sandbox events, to endpoints registered with a test key | Live events, to endpoints registered with a live key |
| Response header | Flint-Mode: test | Flint-Mode: live |
Test and live data never mix. Nothing you created with a test key exists in live mode: not your orders and customers, and not your webhook endpoints, products, plans, promotions, or settings either. Going live is not a data migration. You re-create the handful of resources your integration references, with a live key, and point your production configuration at them.
The most common go-live bug is a test-mode ID left in production configuration. A prod_... or plan_... ID created in a sandbox does not exist in live mode and returns 404. Sweep your config and environment variables for IDs you created while testing, and re-create each one in live mode in Step 4.
Step 1: finish account verification#
Charging real cards requires a verified account. Start on the dashboard's Verify your business page. It asks only for what Stripe needs to turn on payments: your business details, the owner's identity details, and a bank account for payouts. Stripe can ask for more details later, and the same page shows what is due.
Live card payments start as soon as Stripe turns on card payments for your account. Payouts start once Stripe confirms your bank account, which can happen later. Until then, live payments collect in your Flint balance.
If your product embeds onboarding over the API, create a live key first (Step 2). You do not need to verify the business before creating one, and the key can take live card payments once Stripe turns on card payments. Live onboarding over the API needs that live key. The onboarding session token from signup and sandbox keys reach only sandbox onboarding, never the live account. With the live key, read GET /v1/onboarding/state without sandbox_id and follow next_step. Steps the API can complete go through POST /v1/onboarding/advance; steps that need a person, such as identity documents, hand off to an embedded browser session from POST /v1/merchant-account-sessions that you mount in your own product. Omit sandbox_id from these requests too. After the browser exits, read state again and continue. The same loop handles verification requests that arrive after launch. See API & agent onboarding.
You do not have to wait for review to finish before continuing. The readiness check in Step 3 tells you when payments and payouts turn on.
Step 2: create your live key#
Create a live key on the dashboard's API keys page. Live keys start with flint_live_ and are never bound to a sandbox. The prefix alone is what switches your requests into live mode.
Two habits to start with:
- Grant the fewest scopes the integration needs. A checkout backend does not need
settings.write. Scopes are listed in the key scope catalog, and a.writescope already includes its matching.read. - Capture the secret immediately and store it in a secrets manager. The full
secret_keyis shown once, at creation, and never again.
Once one live key exists, you can create additional live keys over the API with POST /v1/api-keys (a live caller omits sandbox_id), and rotate by creating a replacement and revoking the old key. See Manage keys over the API.
Every authenticated response includes a Flint-Mode header, test or live. Assert on it in your deploy smoke test. A test key that reaches production config produces no error, just simulated payments and a Flint-Mode: test header.
Step 3: confirm your account is ready to charge#
Before routing real buyers to checkout, ask Flint whether the account can take payments and receive payouts.
curl https://api.withflintpay.com/v1/merchant \
-H "Authorization: Bearer YOUR_LIVE_KEY"
{
"data": {
"merchant_id": "mer_1kmn0aExample",
"business_name": "Canvas & Co.",
"payments": {
"status": "ready",
"status_reason": null,
"next_actions": []
},
"payouts": {
"status": "ready",
"status_reason": null,
"next_actions": []
},
"requirements": {
"currently_due": [],
"past_due": [],
"eventually_due": [],
"pending_verification": [],
"disabled_reason": null
},
"observed_at": "2026-07-02T18:04:11Z"
}
}
payments must be ready before you launch. payouts can follow later. They answer different questions:
paymentsgates charging.readymeans the account can take live card payments. Anything else means a live buyer cannot pay. While the account ispendingorblocked, creating a payment fails withMERCHANT_ONBOARDING_REQUIRED.payoutsgates money reaching your bank. It can still bependingafterpaymentsisready, while Stripe confirms your bank account. Charges succeed in the meantime, and the funds collect in your balance until payouts areready. If payouts areblocked, therequirementsarrays name what you need to provide.
Each reports a status of ready, pending, blocked, not_available, or not_requested, with a status_reason when it is not ready. A blocked account looks like this:
{
"data": {
"payments": {
"status": "blocked",
"status_reason": "requirements_past_due",
"next_actions": []
},
"requirements": {
"currently_due": [],
"past_due": ["business_website"],
"eventually_due": [],
"pending_verification": [],
"disabled_reason": "requirements_past_due"
}
}
}
The requirements arrays name what is outstanding. Anything in currently_due or past_due needs action from you, in the dashboard or through the onboarding flow from Step 1. Entries in pending_verification are submitted and under review, so re-check later. Items in eventually_due are not blocking yet; handle them before they move up.
For a per-capability breakdown, including individual payout capabilities, use GET /v1/capabilities. Subscribe to capability.updated to hear about changes after launch instead of polling.
If you accept ACH debit, the ACH capability is checked separately in each mode, and Flint support records the settlement and mandate-email evidence per environment. Follow the go-live checklist in ACH debit payments.
Step 4: re-create your configuration in live mode#
Everything your integration references by ID must exist in live mode. The one almost every integration needs is a webhook endpoint.
Register your production webhook endpoint#
Webhook endpoints are per mode: the endpoint you registered while testing receives sandbox events only. Register your production URL with your live key, and you get a fresh signing secret in the response.
curl -X POST https://api.withflintpay.com/v1/webhook-endpoints \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_LIVE_KEY" \
-H "Idempotency-Key: webhook-prod-001" \
-d '{
"url": "https://example.com/webhooks/flint",
"enabled_events": [
"order.paid",
"payment_intent.requires_action",
"payment_intent.processing",
"payment_intent.succeeded",
"payment_intent.payment_failed",
"payment_intent.canceled",
"refund.created",
"refund.updated",
"refund.failed",
"dispute.created",
"capability.updated"
],
"description": "Production endpoint"
}'
{
"data": {
"webhook_endpoint_id": "whep_1kmn0aExample",
"url": "https://example.com/webhooks/flint",
"secret": "whsec_...",
"enabled_events": [
"order.paid",
"payment_intent.requires_action",
"payment_intent.processing",
"payment_intent.succeeded",
"payment_intent.payment_failed",
"payment_intent.canceled",
"refund.created",
"refund.updated",
"refund.failed",
"dispute.created",
"capability.updated"
],
"enabled": true
}
}
Store data.secret. It is your production FLINT_WEBHOOK_SECRET and is only returned here. Do not reuse the sandbox endpoint's secret: each endpoint signs with its own. Omitting enabled_events subscribes the endpoint to every event type. The list above covers fulfillment, payment failures, refunds, disputes, and account status changes; the webhook events catalog has the rest.
Then prove the endpoint works end to end, signature verification included, by sending a synthetic event:
curl -X POST https://api.withflintpay.com/v1/webhook-endpoints/whep_1kmn0aExample/test-events \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_LIVE_KEY" \
-d '{"event_type": "payment_intent.succeeded"}'
{
"data": {
"webhook_event_id": "whev_1kmn0aExample",
"webhook_delivery_id": "wdel_1kmn0aExample",
"webhook_delivery_attempt_id": "watt_1kmn0aExample",
"delivery_status": "delivered",
"attempt_status": "delivered",
"attempt_number": 1,
"delivery_trigger": "test_event",
"status_code": 200,
"duration_milliseconds": 312
}
}
"attempt_status": "delivered" with your endpoint's 200 means your production handler received, verified, and acknowledged a signed Flint event before any real money was involved. If it reports failed instead, the response carries the status code your endpoint returned, an error_summary, and a recommended_action. Fix the handler and send another test event.
Re-create referenced resources#
Beyond webhooks, re-create anything your code or config references by ID, with your live key this time. That usually means the products, variants, and bundles you sell by prod_... ID, any subscription plans and promotions, and any checkout or receipt settings you changed from the defaults while testing.
Processing pricing is not something you re-create. Confirm your live rates under Processing rates on Flint billing before you charge anyone.
Resources you create per request (orders, checkout sessions, payment links, invoices) need no migration. Your backend creates them at runtime.
Step 5: cut over and verify with real money#
The deploy itself is a configuration change:
# Production configuration. The base URL does not change.
FLINT_API_KEY=flint_live_... # Step 2
FLINT_WEBHOOK_SECRET=whsec_... # Step 4, the live endpoint's secret
Plus any live resource IDs from Step 4. Then verify with one real transaction before you announce anything:
- Run your real flow with a real card for $1.00. Test cards decline in live mode.
- Confirm the order settled.
GET /v1/orders/{order_id}shows"status": "closed","payment_status": "paid", andsettlement_amounts.outstanding_moneyat zero. The response carriesFlint-Mode: live. - Confirm the webhook arrived. Your handler received
order.paidfor that order and your fulfillment logic ran. - Check what you netted.
GET /v1/payment-intents/{payment_intent_id}showsprocessing_fee_money, Flint's all-in fee for that payment, andmerchant_net_money, the captured amount minus that fee. See Processing fees. - Refund yourself and confirm the refund events arrive too:
curl -X POST https://api.withflintpay.com/v1/refunds \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_LIVE_KEY" \
-H "Idempotency-Key: golive-refund-001" \
-d '{
"order_id": "ord_1kmn0aExample",
"amount_money": {"amount": 100, "currency": "USD"},
"reason": "requested_by_customer"
}'
One paid-and-refunded dollar exercises your charge path, webhook path, and refund path against production, with real money, once, while you are watching.
The go-live checklist#
Run this list before the first real buyer does it for you.
Credentials#
- Live keys live on your server only: never in browser or mobile code, never in source control. Load them from a secrets manager or environment variables.
- Each key carries only the scopes its service needs.
- Test-mode IDs are gone from production config (Step 4).
- Keys you no longer use are revoked.
Correctness#
- Fulfillment is driven by webhooks or a backend fetch of the order, never by the browser redirect alone. A buyer can close the tab before redirecting, or open your success URL directly.
- Every write your retry logic touches sends an
Idempotency-Key, and retries reuse the same key for the same business action. See Idempotency. - Amounts are confirmed server-side from the order's settlement state, not from anything the client sends you.
- Declines and 3D Secure challenges are handled. Real cards fail more often than test cards, and in more ways. You tested those paths with the decline test cards already.
Webhooks#
- Signatures are verified against the raw request body, and deliveries are deduplicated by the
webhook-idheader (or its legacy equivalent,X-Flint-Webhook-ID). See Webhooks. - Your handler returns
2xxquickly, after durably queuing the work, not after finishing it. - The production secret in your config is the live endpoint's secret from Step 4.
- Someone is watching for delivery failures:
GET /v1/webhook-events?delivery_status=failed.
Operations#
- You log the
request_id(also in theX-Request-Idheader) from every API response, so failures are traceable and support can find your request. - Your workers back off on
429and respectRetry-After. Size them against the rate limits, which count per key and per merchant. - You subscribed to
dispute.createdandcapability.updated, so disputes and account status changes reach you instead of surprising you.
If something fails on launch day#
| Symptom | Likely cause | Fix |
|---|---|---|
| Payments "succeed" but no money moves | A test key is still in production config; responses say Flint-Mode: test | Deploy the flint_live_ key from Step 2 |
| A card that worked in testing is declined | Test cards decline in live mode | Use a real card to verify |
| Webhook signature verification fails | Config still holds the sandbox endpoint's secret | Use the live endpoint's whsec_... from Step 4 |
404 on a product, plan, or promotion | A test-mode ID referenced in live mode | Re-create the resource with your live key (Step 4) |
Creating a payment fails with MERCHANT_ONBOARDING_REQUIRED | The payments readiness status is pending or blocked | Check readiness (Step 3) and clear the listed requirements |
| Live payments succeed but no payout arrives | The payouts readiness status is still pending while Stripe confirms your bank account, or blocked | Check readiness (Step 3). Funds stay in your balance until payouts are ready |
Every failing response includes a request_id. Quote it if you ask Flint Help, and see Debugging for tracing a request end to end.
Next steps#
- Webhooks: signature verification, retries, and delivery monitoring in depth.
- Idempotency: retry-safe writes and which endpoints accept the header.
- Error handling: the error envelope and a production retry strategy.
- Rate limits: the budgets your workers must respect.
- Key security: rotation, secret hygiene, and incident response.
- Authentication: how keys, modes, and auth errors work.
- CLI: production access is explicit, either a live context you approve at
flint loginor--liveon each command with a live API key, and destructive commands prompt before acting. Use it for anything you do to production by hand.
