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.

StepCallHost
1. Tokenize the cardPOST /v1/gateway/card-tokenssecure.breeze.cash
2. Start a risk sessionRisk JS SDK in the customer's browser—
3. Pay with the tokenPOST /v1/gateway/payments with paymentMethod.type: "CARD_TOKEN"secure.breeze.cash
4. Later paymentsPOST /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 to api.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 expiresAt field 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, transactionContext or storeFutureUsage here. 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 new clientReferenceId for 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 new clientReferenceId creates 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:

FieldTypeAlways presentNotes
statusstringYesFAILED
errorCodestringYesOne of the codes below.
errorMessagestringYesHuman-readable description of the error.
{
  "status": "FAILED",
  "errorCode": "WRONG_PARAMETER",
  "errorMessage": "..."
}

Tokenize (POST /v1/gateway/card-tokens):

HTTPerrorCodeMeaning
400WRONG_PARAMETERA 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.
400RESOURCE_NOT_FOUNDcustomer.id doesn't match a customer on your account in this mode.
400RESOURCE_ALREADY_EXISTSThe email is already used by a customer with a different referenceId.
400OPERATION_DENIEDDirect Payment API access isn't enabled for your account. Contact Breeze support.
400AUTHORIZATION_FAILEDThe API key is invalid, the Authorization header is missing, or the request wasn't sent to secure.breeze.cash (or secure-sandbox.breeze.cash).
404ROUTE_NOT_FOUNDWrong path or method.

Pay (POST /v1/gateway/payments with CARD_TOKEN):

HTTPerrorCodeMeaning
400CARD_TOKEN_NOT_FOUNDThe 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.


Did this page help you?