Card Tokens: Tokenize First, Pay Later
Overview
Use card tokens when your card form and your payment step run at different times. You send the card details to Breeze once and receive a card token (ctok_…). You then create the payment with that token instead of the raw card number.
This extends the Direct Payment Integration Guide. Everything in that guide still applies (hosts, authentication, 3DS, the risk SDK, webhooks, response handling) unless this page says otherwise.
| Step | Call | Host |
|---|---|---|
| 1. Tokenize the card | POST /v1/gateway/card-tokens | secure.breeze.cash |
| 2. Start a risk session | Risk JS SDK in the customer's browser | — |
| 3. Pay with the token | POST /v1/gateway/payments with paymentMethod.type: "CARD_TOKEN" | secure.breeze.cash |
| 4. Later payments | POST /v1/gateway/payments with SAVED_CARD (if you saved the card) | secure.breeze.cash |
Prerequisites
- S2S payments enabled, with an API key for each mode (live and sandbox).
- A Breeze customer for every token. Identify the customer by Breeze ID (
customer.id) or by your own customer key (customer.referenceId). Guest customers can't use card tokens.
PCI: card details are only ever sent to
secure.breeze.cash(sandbox:secure-sandbox.breeze.cash). Never send card numbers toapi.breeze.cash.Tokenizing doesn't reduce your PCI scope: you still send the raw card number to Breeze, so the card-data guidance in the Direct Payment Integration Guide still applies. A card token only lets you separate the card form from the payment step.
1. Tokenize the card
Note that this card token is intended to be short-lived, to be used within the current payment session. The expiry time will be returned as part of the
expiresAtfield in the API response.
POST https://secure.breeze.cash/v1/gateway/card-tokens
Authenticate with HTTP Basic, using your API key as the username and an empty password.
const response = await axios.post(
`${secureBaseUrl}/v1/gateway/card-tokens`,
{
customer: {
referenceId: 'user_123', // or: id: 'cus_...'
email: '[email protected]', // optional
firstName: 'Jane', // optional, with lastName
lastName: 'Doe', // optional, with firstName
phone: { countryCode: '1', number: '4155550123' }, // optional
address: { // optional
line1: '1 Market St',
line2: 'Suite 100',
city: 'San Francisco',
state: 'CA',
postalCode: '94105',
country: 'US',
},
},
paymentMethod: {
type: 'RAW_PAN',
card: {
number: '4242424242424242',
expiryMonth: '12',
expiryYear: '2030',
cvv: '123', // optional
},
},
},
{ auth: { username: apiKey, password: '' } }
);For the request and response schema, see Create a card token.
Send the CVV whenever your card form collects it: payments without a CVV are more likely to be declined.
Don't send
amount,currency,threeDs,risk,transactionContextorstoreFutureUsagehere. Those belong on the payment.
The response contains the card token (ctok_…), which you pass to the payment in step 3, and its expiry time. Tokenize again if the token expires before you pay.
A card token is tied to your account, the mode it was created in (live or sandbox), and the customer. A payment for any other customer, or in the other mode, is rejected.
2. Start a risk session
For customer-initiated payments, risk.sessionId is required, exactly as in the Direct Payment Integration Guide. Run the Risk JS SDK in the customer's browser after tokenizing and before creating the payment, then send its session ID with the payment.
3. Pay with the token
POST https://secure.breeze.cash/v1/gateway/payments
The request is the same as a standard card payment, except paymentMethod:
const response = await axios.post(
`${secureBaseUrl}/v1/gateway/payments`,
{
amount: 1999,
currency: 'USD',
clientReferenceId: 'order_1001',
customer: { referenceId: 'user_123' }, // the same customer as the token
paymentMethod: {
type: 'CARD_TOKEN',
tokenId: 'ctok_1a2b3c4d5e6f7a8b',
storeFutureUsage: 'off_session', // optional: save the card
},
transactionContext: { initiator: 'CUSTOMER' },
threeDs: {
mode: 'BREEZE_HOSTED',
successReturnUrl: 'https://example.com/checkout/success',
failReturnUrl: 'https://example.com/checkout/failed',
},
risk: { sessionId },
},
{ auth: { username: apiKey, password: '' } }
);The customer must be the same customer the token was created for, and customer-initiated payments need threeDs and risk.sessionId as usual. For the request schema, see the CARD_TOKEN option of paymentMethod in Create a payment.
Handle the response exactly as described in Handle the Response in the Direct Payment Integration Guide. Always read the inner data.status for the payment outcome.
4. Later payments
If you set storeFutureUsage, the response includes savedPaymentMethodId once the payment is authorized. Use that saved card (SAVED_CARD) for later payments; see Common Integration Patterns in the Direct Payment Integration Guide. Saving the card converts the token, so any later payment with the same token is rejected (see Errors). Since the card tokens have an expiry time, you are advised not to reuse the card tokens outside of the payment session it was created in.
Behaviour to know
- Retrying after a decline: a card token stays valid until
expiresAt, so you can retry a declined payment with the same token. Use a newclientReferenceIdfor the new attempt. - Retrying after a timeout or network error: resend the same request with the same
clientReferenceId. Breeze returns the original result instead of charging again. A newclientReferenceIdcreates a new payment, which can charge the customer twice. - Tokenizing twice creates two separate tokens. An unused token simply expires.
Errors
Errors use the standard envelope:
| Field | Type | Always present | Notes |
|---|---|---|---|
status | string | Yes | FAILED |
errorCode | string | Yes | One of the codes below. |
errorMessage | string | Yes | Human-readable description of the error. |
{
"status": "FAILED",
"errorCode": "WRONG_PARAMETER",
"errorMessage": "..."
}Tokenize (POST /v1/gateway/card-tokens):
| HTTP | errorCode | Meaning |
|---|---|---|
| 400 | WRONG_PARAMETER | A field is missing or invalid, for example an invalid card number, a CVV that isn't 3 or 4 digits, or a field that isn't accepted here. Also returned when the host doesn't match your API key's mode: use secure-sandbox.breeze.cash with sandbox keys and secure.breeze.cash with live keys. |
| 400 | RESOURCE_NOT_FOUND | customer.id doesn't match a customer on your account in this mode. |
| 400 | RESOURCE_ALREADY_EXISTS | The email is already used by a customer with a different referenceId. |
| 400 | OPERATION_DENIED | Direct Payment API access isn't enabled for your account. Contact Breeze support. |
| 400 | AUTHORIZATION_FAILED | The API key is invalid, the Authorization header is missing, or the request wasn't sent to secure.breeze.cash (or secure-sandbox.breeze.cash). |
| 404 | ROUTE_NOT_FOUND | Wrong path or method. |
Pay (POST /v1/gateway/payments with CARD_TOKEN):
| HTTP | errorCode | Meaning |
|---|---|---|
| 400 | CARD_TOKEN_NOT_FOUND | The token doesn't exist, has expired, or belongs to a different customer or mode: tokenize the card again. Also returned when an earlier payment saved the card with storeFutureUsage, which converts the token; the errorMessage says so. In that case, pay with SAVED_CARD and the savedPaymentMethodId from that payment instead of tokenizing again. |
All other payment errors and decline fields (errorCode, failureReason, adviceCode) are the same as for standard card payments.
Testing in sandbox
Use https://secure-sandbox.breeze.cash with your sandbox API key. Tokens created in sandbox only work for sandbox payments. Tokenize the sandbox test cards listed in Testing in Sandbox in the Direct Payment Integration Guide.
Updated about 19 hours ago
