From test mode to live
Live is a separate space. Redo each line below with the dashboard on Live, then swap the key.
0 of 7 done
a live key (
sk_live_…), created in the dashboard switched to livea test key never sees or creates live data
the receiving wallets on the mainnets, per chain (dashboard:
POST /wallets,POST /), and the currencies they accept, before any checkout (a space whose wallets accept nothing opens none:token-wallets 409 merchant_; check withnot_ ready GET /)wallets/ accepted-currencies test wallets are for test networks; the organization fee also needs the organization's own wallet on each chain (
GET /,organization/ wallets routes/)members. md created again in live (new
uuids)references may be the same as in test (unique per space and mode)
created again in live, each with its own new secret
test endpoints only receive test events; store the live secret separately
invited again in live (
POST /with a live key); their fee and trust are per modeinvitations a member of test only →
404in liveset in live (
PUT /with a live credential); the dashboard can apply one change to both modessettings/ :key values are per space and mode
nothing to do
owners and admins work in both modes
Then switch your integration
Avoid
Branch your code on the mode
Do
Swap the key, not the code
sk_live_…) from the environment; the same code runs in both modes.Check which key you hold with the client's first call:
client-init.sh
# Every request sends your API key. The start-up check: what is this key, and which mode is it in?curl https://api.qbitflow.app/v2/me \ -H "X-API-Key: $QBITFLOW_API_KEY"# 200: { "credential": "apiKey", "role": "admin", "space": { "test": true, … }, … }// One client per API key, shared by the whole program. A blank key, one not starting with// sk_, or one with a space or a control character, throws a ValidationError (nothing is sent).const client = new QBitFlow( process. env. QBITFLOW_API_KEY ?? ''); // The recommended start-up check: what is this key, and which mode is it in? An// AuthenticationError for an unknown or revoked key.const me = await client.me();console.log(`${me.role} key, test mode: ${me.space?.test}`);# One client per API key, shared by the whole program. ValidationError: not an sk_… key.client = qbitflow. QBitFlow( os. environ["QBITFLOW_API_KEY"]) # The recommended start-up check: what is this key, and which mode is it in?me = client.me() # AuthenticationError for an unknown or revoked keyif me.space is not None: print( f"{me. space. organization_name}, role {me.role}, test mode {me.space.test}")// One client per API key, shared by the whole program: it is safe for concurrent use.client, err := qbitflow. NewClient( os. Getenv( "QBITFLOW_API_KEY"))if err != nil { log.Fatal(err) // a *qbitflow.ValidationError: no key, or not an sk_… key} // The recommended start-up check: what is this key, and which mode is it in?me, err := client.Me(ctx)if err != nil { log.Fatal(err) // a *qbitflow.AuthenticationError for an unknown or revoked key}if me.Space != nil { fmt.Printf("%s, role %s, test mode %v\n", me.Space.OrganizationName, me.Role, me.Space.Test)}// One client per API key, shared by the whole application.$client = new \QBitFlow\QBitFlow(apiKey: (string) getenv('QBITFLOW_API_KEY')); // The recommended start-up check: which space and mode is this key in?$me = $client->me(); // an AuthenticationException for an unknown or revoked keyprintf("%s, role %s, %s mode\n", $me->space?->organizationName ?? '?', $me->role ?? '?', $me->space?->test ? 'test' : 'live');- Store the live webhook secret beside the test one, never instead: each endpoint has its own.
httpsredirects. A live checkout'ssuccessUrlandcancelUrl(yours, orcheckout.defaults') must behttps://.- Re-create what you referenced by id. Live products and customers get new
uuids; references you chose may stay the same.
What behaves differently in live
Differences to expect in live: the subscription frequency minimum is 1 hour (5 minutes in
test); subscriptions are billed on their due date by QBitFlow (test subscriptions only when you
trigger their bill, POST /subscriptions/:uuid/test-bill, Subscriptions and bills); customer
emails are sent (never in test mode); faucets (POST /faucets/:chain) and test payments
(POST /payments/test) answer 400 test_mode_only.
- Prices: test mode caps them at $5, live has no cap. Test payments are made with faucet
tokens, whose supply is limited, so every test price is at most $5: a product's (create and
update), a checkout's inline
priceUsd(payments and subscriptions), and a test product's at checkout. Above →400validation_failedon the field (priceUsd, orproductUuid/productReference),details.max: "5"(Products, Checkout sessions). Live prices are only bounded by what the chosen token can hold (Payments and transactions). - Redirects: live checkouts redirect over
httpsonly. A live checkout'ssuccessUrlandcancelUrl(yours, or thecheckout.defaultssetting's) must behttps://→ else400validation_failedon the field. Test mode takeshttp://(a local shop): check your live URLs and the livecheckout.defaultsbefore going live.
Your first live payment
Take one small real payment end to end before you announce anything: your wallet receives it,
payment.completed arrives, the order is fulfilled once. Refund it from the dashboard if you like,
and watch refund.approved arrive.