A customer session is a buyer-scoped credential. Your backend authenticates the buyer however you already do, then asks Flint for a session bound to one customer. The session secret is the only credential accepted by /v1/me, and the customer it names is the only customer those endpoints will read or write.
Creating a session needs a secret API key with customers.sessions.write. Everything the session can then do is fixed by Flint, not chosen by the caller: there is no scope argument, and no way to widen it.
Three windows#
One create call returns up to three credentials, each with its own lifetime.
| Credential | 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 |
account_url is returned only when the merchant uses Flint's hosted account. It is a one-time link that signs the buyer in, so it is the shortest-lived of the three and belongs in a redirect, never in stored state.
secret (flint_cses_) is the working credential. Send it as Authorization: Bearer.
refresh_token (flint_cref_) buys a new pair when the secret expires.
curl -X POST https://api.withflintpay.com/v1/customer-sessions \
-H "Authorization: Bearer $FLINT_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"customer_id": "cus_01JQEXAMPLE0000000000000000"}'
Refresh rotates both credentials#
POST /v1/customer-sessions/refresh takes the refresh token and nothing else. It needs no API key, which is what lets a buyer-facing backend session keep itself alive without holding your secret key on that path. It requires an Idempotency-Key.
Every refresh invalidates the token it consumed and returns a new secret and a new refresh token. Store the newest pair and serialize concurrent refreshes, because two parallel refreshes with the same token means the second one is a reuse.
Presenting a refresh token that has already been rotated away revokes the entire session family and returns CUSTOMER_SESSION_REFRESH_REUSED. That is the intended response to a stolen token, so treat it as a signal rather than a transient failure: sign the buyer out and re-authenticate them. Do not retry.
| Error | Meaning | What to do |
|---|---|---|
CUSTOMER_SESSION_EXPIRED | The secret aged out | Refresh, then retry the call |
CUSTOMER_SESSION_REFRESH_EXPIRED | The refresh token aged out | Re-authenticate the buyer and create a new session |
CUSTOMER_SESSION_REFRESH_REUSED | A rotated token was presented again | Re-authenticate the buyer. The family is already revoked |
INVALID_CUSTOMER_SESSION | The credential is malformed or unknown | Re-authenticate the buyer |
CUSTOMER_SESSION_REQUIRED | A /v1/me call arrived without a session secret | Send the secret as a bearer token |
Revocation#
Revoke one session when a buyer signs out of one device. Revoke every session for a customer when the buyer changes their password, when you close their account, or when you suspect a takeover.
curl -X POST https://api.withflintpay.com/v1/customers/cus_01JQEXAMPLE0000000000000000/sessions/revoke \
-H "Authorization: Bearer $FLINT_SECRET_KEY"
Revoking customer sessions does not sign the buyer out of Flint's own hosted account, and signing out of the hosted account does not revoke your customer sessions. The two are independent.
Keep the secret on your server#
A customer session secret is a server-side credential with the same handling rules as an API key. It does not belong in browser code, a mobile bundle, a URL, or a log. The browser talks to your backend, and your backend talks to Flint.
Flint does not currently accept a customer session from a public client.
