Going Live
Your integration works in a sandbox. This guide takes it to production, in order: finish account verification, mint a live key, confirm your account is ready to charge, re-create your live configuration, then cut over and verify with real money.
The cutover itself is deliberately small. Flint has one hostname and one API for both modes, so nothing about your code changes: 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 |
Test and live are two fully separate data planes. Nothing you created with a test key exists in live mode: not your orders and customers, and also not your webhook endpoints, products, plans, coupons, or settings. Going live is not a data migration. It is re-creating the handful of resources your integration references, with a live key, and pointing 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: your business profile, identity verification, and a bank account for payouts. Complete this in the dashboard, which walks you through exactly what is still required.
If your integration is headless, the same verification runs over the API through the onboarding state machine: read GET /v1/onboarding/state and follow next_step through POST /v1/onboarding/advance calls. See API & Agent Onboarding. The state machine also handles later compliance remediation, so keep it in your toolbox after launch.
You do not have to wait for review to finish before continuing; the readiness check in Step 3 tells you when the account clears.
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 worth keeping from day one:
- Grant the fewest scopes the integration needs. A checkout backend does not need
settings.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 mint 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 Managing Keys Over the API.
Every authenticated response includes a Flint-Mode header (test or live). It is the fastest way to prove which mode a request actually ran in, and a cheap assertion for your deploy smoke test: a test key that sneaks into 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 at checkout, ask Flint directly whether the account can take payments and receive payouts. Your merchant ID is in the Flint-Merchant-Id response header of any authenticated call, and in the response that created your key.
curl https://api.withflintpay.com/v1/merchants/mer_1kmn0aExample/readiness \
-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"
}
}
The two axes answer different questions, and you want both ready before launch:
paymentsgates charging. Anything other thanreadymeans live checkouts will have no payment methods to offer.payoutsgates money reaching your bank. Payments can bereadywhile payouts are still blocked; charges then succeed but the funds accumulate in your balance until the payout requirements clear. Catch that before launch, not on payday.
Each axis reports a status of ready, pending, blocked, or not_available, 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 exactly 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 already submitted and under review: nothing to do but re-check. eventually_due items are not blocking yet; handle them before they move up.
For a per-capability breakdown (for example the payout capabilities individually), use GET /v1/capabilities. To ask what a specific transaction could offer at checkout, amount and country included, use POST /v1/payment-options/resolve; see the payment options reference.
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.payment_succeeded",
"payment_intent.payment_failed",
"refund.created",
"dispute.created",
"capability.updated"
],
"description": "Production endpoint"
}'
{
"data": {
"webhook_endpoint_id": "whep_1kmn0aExample",
"url": "https://example.com/webhooks/flint",
"secret": "whsec_...",
"enabled_events": [
"order.payment_succeeded",
"payment_intent.payment_failed",
"refund.created",
"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 is a sensible minimum that covers fulfillment, failures, refunds, disputes, and account status changes. Browse the full list in the webhook events catalog.
Then prove the pipe 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",
"event_status": "delivered",
"attempt_status": "delivered",
"attempt_number": 1,
"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 a failure instead, the response includes the status code your endpoint returned; fix and re-send.
Re-create Referenced Resources#
Beyond webhooks, re-create anything your code or config references by ID, with your live key this time:
- Catalog: products, variants, and bundles you sell by
prod_...ID. - Billing: subscription plans and coupons.
- Settings: anything you changed from defaults while testing, such as processing or receipt settings.
Hosted flows you create per request (orders, checkout sessions, payment links, invoices) need no migration; they are created fresh by your backend 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 announcing anything:
- Run your real flow with a real card for a small amount. Test cards decline in live mode.
- Confirm the order settled:
GET /v1/orders/{order_id}shows"status": "paid"with nothing outstanding, and the response carriesFlint-Mode: live. - Confirm the webhook arrived: your handler received
order.payment_succeededfor that order and your fulfillment logic fired. - Refund yourself and confirm the refund events flow 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. That is the whole point of the step: every surface you will rely on has now worked with real money, once, while you were 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 hit 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 gracefully. Real cards fail more often and more creatively than test cards; you tested those paths with the decline test cards already.
Webhooks#
- Signatures are verified against the raw request body, and deliveries are deduplicated by
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?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 coupon | A test-mode ID referenced in live mode | Re-create the resource with your live key (Step 4) |
| Checkout shows no payment methods | The payments readiness axis is not ready | Check readiness (Step 3) and clear the listed requirements |
Every failing response includes a request_id; quote it if you contact support, 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.
