Customer sessions
A customer session lets your backend act as one buyer, and only that buyer. It is what makes a customer account you build yourself as safe as the one Flint hosts: ownership checks stay on Flint's side, so a mistake in your front end cannot show one buyer another buyer's order.
You authenticate the buyer however you already do. Flint never sees your password, your identity provider, or your login form.
Mint a session#
Your backend calls Flint with a secret API key holding customers.sessions.write, naming the customer it has just authenticated.
curl -X POST https://api.withflintpay.com/v1/customer-sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "customer_id": "cus_1kmn0aExample" }'
{
"customer_session_id": "cses_1kmn0aExample",
"customer_id": "cus_1kmn0aExample",
"secret": "flint_cses_example",
"expires_at": "2026-08-10T18:00:00Z",
"refresh_token": "flint_cref_example",
"refresh_token_expires_at": "2026-08-17T17:00:00Z"
}
secret is the working credential. Send it as Authorization: Bearer to /v1/me and nowhere else; no other endpoint accepts it.
There is no scope argument. What a customer session can do is fixed by Flint, and it is the same set whether you host the account or Flint does.
Three lifetimes, three jobs#
| Credential | Request field | Default | Range |
|---|---|---|---|
account_url | account_url_expires_in_seconds | 15 minutes | 1 minute to 1 hour |
secret | expires_in_seconds | 1 hour | 5 minutes to 24 hours |
refresh_token | refresh_expires_in_seconds | 7 days | 1 hour to 30 days |
The defaults are the right answer for most integrations. Shorten expires_in_seconds if you want tighter blast radius per request; lengthen refresh_expires_in_seconds if you want buyers to stay signed in for longer between logins.
account_url only comes back when the merchant uses Flint's hosted account. It is a one-time link that signs the buyer straight in, which is how you hand a buyer from your site to Flint's account without a second login. Redirect to it immediately. It is the shortest-lived credential in the response for a reason, and it does not belong in stored state or an email.
Refresh rotates both credentials#
When secret expires, trade the refresh token for a new pair. This call takes no API key, so the path that keeps a buyer session alive never needs your secret key.
curl -X POST https://api.withflintpay.com/v1/customer-sessions/refresh \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refresh-cses-1kmn0aExample-004" \
-d '{ "refresh_token": "flint_cref_example" }'
Idempotency-Key is required here, not optional.
Every refresh invalidates the token it consumed. Store the new pair before you use it, and serialize refreshes for one session, because two parallel refreshes holding the same token means the second one is a replay.
Reusing a rotated refresh token revokes everything
Presenting a refresh token that has already been exchanged returns CUSTOMER_SESSION_REFRESH_REUSED and revokes the whole session family, including the credentials the legitimate holder is using.
This is the designed response to a stolen token: Flint cannot tell the thief from the buyer, so it ends both. Treat it as a security signal, not a transient failure. Sign the buyer out and make them log in again. Do not retry.
Handle the four failures#
| Error | Meaning | What to do |
|---|---|---|
CUSTOMER_SESSION_EXPIRED | The secret aged out | Refresh, then retry the original call |
CUSTOMER_SESSION_REFRESH_EXPIRED | The refresh token aged out | Re-authenticate the buyer, mint a new session |
CUSTOMER_SESSION_REFRESH_REUSED | A rotated token came back | Re-authenticate. The family is already revoked |
INVALID_CUSTOMER_SESSION | Malformed or unknown credential | Re-authenticate |
Only the first is worth retrying. The other three all mean the same thing to your application: this buyer needs to log in again.
Revoke on sign-out and on suspicion#
Revoke one session when a buyer signs out of one device:
curl -X POST https://api.withflintpay.com/v1/customer-sessions/cses_1kmn0aExample/revoke \
-H "Authorization: Bearer YOUR_API_KEY"
Revoke every session for a customer when the buyer changes their password, when you close their account, or when you think the account has been taken over:
curl -X POST https://api.withflintpay.com/v1/customers/cus_1kmn0aExample/sessions/revoke \
-H "Authorization: Bearer YOUR_API_KEY"
Neither call signs the buyer out of Flint's own hosted account, and signing out there does not revoke your customer sessions. The two are independent, which matters if you run both surfaces during a migration.
Keep the secret on your server#
Session secrets are server-side credentials
A customer session secret is a server-side credential with exactly the same handling rules as an API key. Not in browser code, not in a mobile bundle, not in a URL, not in a log line.
Your browser calls your backend; your backend calls Flint. Flint does not currently accept a customer session from a public client.
Rate limits#
Customer sessions are rate limited per session and per customer, separately from your merchant key. One buyer refreshing in a loop cannot consume your integration's budget.
Next steps#
- Build your own customer account for what the credential unlocks
- Customer accounts for the settings that decide where buyers land
- API key security for handling the key that mints sessions
