Tokenization

Encrypt card data in the browser with Tokeflow's Bridge SDK and exchange it for a single-use token reference your backend can charge.

Tokenization turns raw card data into a safe-to-handle token reference without that data ever touching your servers. With the Bridge SDK, the cardholder types into Tokeflow's secure fields, the SDK encrypts the data in the browser, and tokeflow.cards.tokenize() returns a CardToken. You send only the tokenId to your backend, which creates a transaction with it.

This keeps sensitive card data out of your application's scope while letting you keep full control of the UI and the payment flow.

Tokeflow's secure fields run inside isolated, cross-origin iframes; card data is encrypted in the browser and never touches your servers. Page scripts cannot read the raw PAN, expiry, or CVC.

How tokenization fits in

Tokenization is a client-side step. The token it produces is exchanged for a charge on your server, where your secret key lives.

Never send raw card data (PAN, CVC, expiry) to your own backend. Send only the tokenId returned by tokenize(). The whole point of tokenization is to keep card data out of your servers' PCI scope.

tokeflow.cards.tokenize(input, options?)

The cards.tokenize() method encrypts the data collected by your card Elements and resolves to a CardToken. It accepts two input shapes — combined or split — plus optional cardholder name and billing address.

Call tokenize() only after the SDK is initialized (await tokeflow.init()) and your card element(s) are mounted. See Quickstart for the full setup.

Combined-element input

Use the combined CardElement (number, expiry, and CVC in a single iframe) and pass it as card:

const cardElement = tokeflow.createCardElement();
cardElement.mount('#card-element');

const token = await tokeflow.cards.tokenize({ card: cardElement });
console.log(token.tokenId); // tok_...

Split-fields input

When you collect the card number, expiry, and CVC in separate iframes, pass each element under its own key:

const cardNumber = tokeflow.createCardNumberElement();
const cardExpiry = tokeflow.createCardExpirationElement();
const cardCvc = tokeflow.createCardCvcElement();

cardNumber.mount('#card-number');
cardExpiry.mount('#card-expiry');
cardCvc.mount('#card-cvc');

const token = await tokeflow.cards.tokenize({
  cardNumber,
  expiration: cardExpiry,
  cvc: cardCvc,
});

The expiration key is expiration — not expirationDate or expiry. Using the wrong key leaves the expiry unset and tokenization fails validation.

Optional cardholder name and billing address

Both inputs accept an optional cardholderName and billingAddress. Each field can be supplied as a mounted element or as a plain string — mix and match as your form requires.

const token = await tokeflow.cards.tokenize({
  card: cardElement,
  cardholderName: cardholderNameElement, // element or 'Jane Doe'
  billingAddress: {
    line1: addressLine1Element,          // element or string
    line2: 'Apt 4B',                     // string
    city: cityElement,                   // element or string
    state: 'CA',                         // string
    postalCode: postalCodeElement,       // element or string
    country: 'US',                       // ISO 3166-1 alpha-2
  },
});
FieldTypeRequiredDescription
cardCardElementConditionalThe combined card element. Required for combined input.
cardNumberCardNumberElementConditionalCard-number element. Required for split input.
expirationCardExpirationElementConditionalExpiry element. Required for split input. Note the key is expiration.
cvcCardCvcElementConditionalCVC element. Required for split input.
cardholderNameCardholderNameElement | stringNoName on the card, as a mounted element or a string.
billingAddressBillingAddressInputNoBilling address. Each subfield is an element or a string.

BillingAddressInput subfields: line1, line2, city, state, postalCode, country — every one accepts an element or a string.

Options

The optional second argument carries non-sensitive metadata to store alongside the token, and — since SDK 2.6.0 — says whether the card may be saved for later.

type TokenPurpose = 'one_time' | 'instrument_enrollment';

interface TokenizeOptions {
  metadata?: Record<string, any>; // custom metadata stored with the token
  saveCard?: boolean;             // inside a checkout session: enroll the card for later use
  purpose?: TokenPurpose;         // standalone tokenization: which kind of token to mint
}
OptionWhere it appliesEffect
saveCardOnly inside a checkout session (checkoutSessionId set).For a one-time offer, true mints an instrument_enrollment token so the card can be saved as a payment instrument. A recurring offer always enrolls, so the flag is redundant there. Ignored outside a session.
purposeOnly for standalone tokenization (no checkout session).one_time (default) mints a single-use token that expires; instrument_enrollment mints a token with no expiry that can be saved. Inside a checkout session the server derives the purpose from the offer and saveCard, and ignores this.
metadataEverywhere.Non-sensitive custom data stored with the token.
// Inside a checkout session — the customer ticked "save my card"
const token = await tokeflow.cards.tokenize(
  { card: cardElement },
  { saveCard: true, metadata: { cartId: 'cart_123' } }
);

// Standalone tokenization — mint a token that can be saved
const token = await tokeflow.cards.tokenize(
  { card: cardElement },
  { purpose: 'instrument_enrollment' }
);

Both options are omitted from the request when unset, so leaving them out is not the same as sending false. Ask for the customer's consent before requesting an enrollable token.

Never put cardholder PII or card data in metadata. It is non-sensitive custom data only.

tokenize() always returns a CardToken; it never saves the card by itself. Whether the card becomes a reusable instrument is decided server-side — by the checkout session, by save_payment_instrument on the charge, or by an explicit enrollment call — and each of those refuses a one_time token. Read the purpose on the returned token to know what you got. See One-click upsells & downsells for the full flow.

The CardToken response

interface CardToken {
  tokenId: string;       // your Tokeflow token reference, e.g. 'tok_...'
  type: 'card';
  brand: string;         // 'visa', 'mastercard', etc.
  last4: string;         // last 4 digits of the card
  expiryMonth: string;   // e.g. '12'
  expiryYear: string;    // e.g. '2029'
  bin?: string;          // bank identification number (optional)
  createdAt?: string;    // ISO 8601 timestamp (optional)
  purpose?: TokenPurpose; // 'one_time' | 'instrument_enrollment' — what the server actually minted
}
FieldTypeDescription
tokenIdstringThe token reference to send to your backend. Prefixed tok_.
type'card'Always card for card tokenization.
brandstringDetected card brand (e.g. visa, mastercard).
last4stringLast four digits, safe to display.
expiryMonthstringTwo-digit expiry month as a string.
expiryYearstringFour-digit expiry year as a string.
binstring?Bank identification number, when available.
createdAtstring?ISO 8601 creation timestamp, when present.
purposeTokenPurpose?The purpose the server minted the token with — one_time or instrument_enrollment. Check it when you asked for an enrollable token: a mismatch fails nothing here but is refused later, at enrollment or charge time.

Example value:

{
  "tokenId": "tok_8f3c2a1b9d4e",
  "type": "card",
  "brand": "visa",
  "last4": "4242",
  "expiryMonth": "12",
  "expiryYear": "2029",
  "bin": "424242",
  "createdAt": "2026-01-15T12:30:00.000Z",
  "purpose": "one_time"
}

brand, last4, bin, and the expiry are non-sensitive and safe to display in your UI — useful for an "ending in 4242" confirmation. The full PAN and CVC are never returned.

Test the Bridge SDK without code

Want a token to try the API before wiring up the SDK? Tokeflow hosts a tester at card.tokeflow.com that runs the Bridge SDK against your merchant and shows you the returned token — the same encrypted tok_… reference your own integration receives. Use it to see exactly what tokenization returns and to feed real token values into the payment endpoints.

  1. Open card.tokeflow.com and enter your merchant's public key (pk_test_… or pk_live_…). Secret keys are never used in the browser.
  2. Complete the secure card fields and submit. The card is encrypted inside Tokeflow's isolated, cross-origin iframes — exactly as on your own site — so the raw PAN never leaves the browser.
  3. Inspect the returned token reference (tok_…) together with its non-sensitive details (brand, last4, expiry).
  4. Pass that value as card_ciphertext_id to test your payment-method endpoints — for example POST /api/v1/transactions.

Each token is single-use and short-lived — generate a fresh one for every test charge. Use a test public key (pk_test_…) while integrating so no real funds are involved. See Environments and keys.

Exchanging the token for a charge

The token is a reference, not a charge. After tokenize() resolves, send the tokenId to your own backend and let your server create the transaction with its secret key.

1. Tokenize in the browser

const token = await tokeflow.cards.tokenize({ card: cardElement });

await fetch('/api/payments', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ tokenId: token.tokenId }),
});

2. Create the transaction from your server

Your backend exchanges the tokenId for a transaction. Use a secret key — never expose it in the browser — and send an Idempotency-Key so retries don't double-charge.

curl -X POST https://api.tokeflow.com/api/v1/transactions \
  -H "Authorization: Bearer sk_live_mer_xxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6b1f7e2c-9a44-4f1e-8c3d-1a2b3c4d5e6f" \
  -d '{
    "amount": 9900,
    "currency": "BRL",
    "payment_method": "credit_card",
    "card_ciphertext_id": "tok_8f3c2a1b9d4e",
    "customer_id": "cust_9f2a1c"
  }'

Example success response:

{
  "success": true,
  "data": {
    "id": "tx_4d2e1a9c7b",
    "status": "authorized",
    "amount": 9900,
    "currency": "BRL",
    "payment_method": "credit_card",
    "charge_type": "payment",
    "customer_id": "cust_9f2a1c",
    "created_at": "2026-01-15T12:30:01.000Z"
  },
  "request_id": "req_8f3c2a1b",
  "timestamp": "2026-01-15T12:30:01.000Z"
}

The amount is in integer minor units (cents). 9900 with "currency": "BRL" is R$99.00. Always send the ISO 4217 currency.

Money amounts and routing decisions are made on your server with your secret key. The browser never sees the secret key, the amount it cannot tamper with, or the connector that handles the charge. See Authentication for key scoping.

Error handling

tokenize() rejects with a TokeflowError carrying a machine-readable code. Handle the common cases to give the cardholder actionable feedback. See the full list on Error Handling.

import { TokeflowError, TokeflowErrorCode } from '@tokeflow_com/bridge-js';

try {
  const token = await tokeflow.cards.tokenize({ card: cardElement });
  // ... send token.tokenId to your backend
} catch (error) {
  if (error instanceof TokeflowError) {
    switch (error.code) {
      case TokeflowErrorCode.ELEMENT_INCOMPLETE:
        showError('Please complete all card fields');
        break;
      case TokeflowErrorCode.INVALID_CARD:
        showError('Please check your card number');
        break;
      case TokeflowErrorCode.EXPIRED_CARD:
        showError('This card has expired');
        break;
      case TokeflowErrorCode.UNSUPPORTED_CARD_BRAND:
        showError('This card brand is not supported');
        break;
      case TokeflowErrorCode.NETWORK_ERROR:
        showError('Network error. Please try again.');
        break;
      default:
        showError(error.message);
    }
  }
}
CodeWhen it happens
ELEMENT_INCOMPLETEA required card field is not fully entered.
ELEMENT_INVALIDAn element's value is invalid.
INVALID_CARDThe card number failed validation.
EXPIRED_CARDThe card's expiry date is in the past.
UNSUPPORTED_CARD_BRANDThe card brand is not enabled for this merchant.
TOKENIZATION_FAILEDTokenization could not be completed.
NOT_INITIALIZEDtokenize() was called before init() resolved.
ELEMENT_NOT_MOUNTEDA passed element was not mounted to the DOM.
NETWORK_ERRORThe request to Tokeflow failed at the network layer.

Read the element's getState() ({ empty, valid, invalid, complete }) before calling tokenize(), and disable your pay button until state.complete && state.valid. This catches incomplete input before a round trip. See Elements.

Next steps

  • Elements — create and style the secure fields you pass to tokenize().
  • One-click upsells & downsells — save the card during the purchase and charge it again with one click.
  • Checkout — link a backend-created session to drive a full checkout flow.
  • Error Handling — the complete TokeflowErrorCode reference.
  • Authentication — public vs. secret keys and key scoping.

On this page