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 modeLive mode
Keyflint_test_..., bound to a sandboxflint_live_..., never sandbox-bound
DataIsolated test dataYour real merchant data
CardsTest cards onlyReal cards only; test cards decline
MoneySimulated; nothing movesReal charges, refunds, and payouts
WebhooksSandbox events, to endpoints registered with a test keyLive 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 .write scope already includes its matching .read.
  • Capture the secret immediately and store it in a secrets manager. The full secret_key is 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.

Bash
curl https://api.withflintpay.com/v1/merchants/mer_1kmn0aExample/readiness \
  -H "Authorization: Bearer YOUR_LIVE_KEY"
JSON
{
  "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:

  • payments gates charging. Anything other than ready means live checkouts will have no payment methods to offer.
  • payouts gates money reaching your bank. Payments can be ready while 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:

JSON
{
  "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.

Bash
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"
  }'
JSON
{
  "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:

Bash
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"}'
JSON
{
  "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:

Bash
# 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:

  1. Run your real flow with a real card for a small amount. Test cards decline in live mode.
  2. Confirm the order settled: GET /v1/orders/{order_id} shows "status": "paid" with nothing outstanding, and the response carries Flint-Mode: live.
  3. Confirm the webhook arrived: your handler received order.payment_succeeded for that order and your fulfillment logic fired.
  4. Refund yourself and confirm the refund events flow too:
Bash
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 2xx quickly, 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 the x-request-id header) from every API response, so failures are traceable and support can find your request.
  • Your workers back off on 429 and respect Retry-After. Size them against the rate limits, which count per key and per merchant.
  • You subscribed to dispute.created and capability.updated, so disputes and account status changes reach you instead of surprising you.

If Something Fails on Launch Day#

SymptomLikely causeFix
Payments "succeed" but no money movesA test key is still in production config; responses say Flint-Mode: testDeploy the flint_live_ key from Step 2
A card that worked in testing is declinedTest cards decline in live modeUse a real card to verify
Webhook signature verification failsConfig still holds the sandbox endpoint's secretUse the live endpoint's whsec_... from Step 4
404 on a product, plan, or couponA test-mode ID referenced in live modeRe-create the resource with your live key (Step 4)
Checkout shows no payment methodsThe payments readiness axis is not readyCheck 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.
Rate this doc