A token is a stand-in for a card number that is useless to anyone who steals it. Your system stores the token; the provider stores the card. If your database is breached, the attacker gets a list of identifiers that only work against your own account with that provider — not card numbers they can spend.

That single property is why tokenisation is the default architecture for anything storing a card, and why the alternative — encrypting card numbers yourself — is a harder problem with a worse outcome.

Tokenization is not encryption

The two get used interchangeably and they are structurally different:

EncryptionTokenization
ReversibleYes, with the keyNo — the token has no mathematical relationship to the number
If your store is breachedThe attacker needs the key, which may be breached alongside itThe token alone is worthless
PCI scopeAn encrypted PAN is still cardholder data, still in scopeThe token is generally out of scope
FormatCiphertextOften format-preserving, so it looks like a card number

The row that matters is the third. Encrypting card numbers is real work — key management, rotation, access control, an audit trail — and at the end of it you still hold cardholder data and still carry the scope that comes with it. The PCI guide sets out what that entails. Tokenisation sidesteps the whole category by not holding the data.

The format-preserving row is worth a warning too. Because tokens frequently look like card numbers — right length, sometimes a plausible prefix, sometimes even Luhn-valid — code that sees one may treat it as a PAN and log it, mask it, or validate it. That is harmless but confusing, and it is why token values should be labelled as such in your schema rather than sharing a column type with card numbers.

The two kinds of token

This is the distinction most explanations skip, and it has real operational consequences.

Gateway tokens are issued by your payment provider: Stripe’s pm_… payment methods, Braintree’s payment method tokens, and the equivalents elsewhere. They are provider-specific and not portable. If you change providers, you migrate — most providers support a provider-to-provider migration, but it is a project rather than a config change, and it is worth knowing that before you have a million saved cards.

Network tokens are issued by the card networks themselves: the Visa Token Service, Mastercard’s MDES, and American Express’s equivalent. They sit one level up, and that buys three things:

  • The token survives card reissuance. When a customer’s card is lost, replaced or simply expires, the network repoints the token at the new card. On a subscription business this is the difference between a customer churning silently and never noticing anything happened.
  • Approval rates are generally higher. Issuers treat network-tokenised transactions as lower risk, because the token carries cryptographic provenance the network vouches for.
  • Wallets already use them. Apple Pay and Google Pay are network tokenisation with a device-specific token and a per-transaction cryptogram, which is why a wallet payment exposes nothing reusable even if intercepted.

Most merchants reach network tokens through their gateway rather than directly — the gateway enrols the card and manages the network relationship. Ask your provider whether it is enabled, because it often is not by default and the reissuance benefit alone usually justifies turning it on.

How a token is created

The flow, and the property that makes it work:

  1. The customer types their card into a field rendered by the provider — a hosted field or an iframe, not an input you own.
  2. That field sends the card directly to the provider, bypassing your server entirely.
  3. The provider returns a token to the browser.
  4. Your code sends the token to your server and stores it.

The card number never touches your infrastructure. Not in a request body, not in a log, not in memory. That is not a policy you enforce; it is a consequence of where the input element lives.

// Stripe Elements: the card details go from the iframe to Stripe, never through you.
const stripe = Stripe(process.env.STRIPE_PUBLISHABLE_KEY);
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');

form.addEventListener('submit', async (event) => {
  event.preventDefault();

  const { paymentMethod, error } = await stripe.createPaymentMethod({
    type: 'card',
    card: cardElement,
  });

  if (error) {
    showError(error.message);   // never surface a raw decline code to the customer
    return;
  }

  // paymentMethod.id is "pm_..." — this is what your server stores.
  // paymentMethod.card gives you brand and last4 for display, without the number.
  await fetch('/api/save-payment-method', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ paymentMethodId: paymentMethod.id }),
  });
});

Two details in that snippet do real work. The card object returned alongside the token carries the brand and last four digits, which is everything a “saved cards” list needs — you never need the number to show a customer which card they used. And nothing card-shaped appears in the request to your own API, which is what keeps that endpoint out of scope. The Elements documentation and the payment methods reference cover the rest of the API surface.

Using tokens

  • One-off payments — tokenise, charge, discard. The customer is present and authenticating.
  • Card on file — store the token against the customer, charge it later with their agreement.
  • Subscriptions — the same, on a schedule. This is where network tokens earn their keep.
  • Merchant-initiated transactions — charges the customer is not present for. These must be flagged as MITs, because an unflagged one may be challenged for authentication when nobody is there to answer; the 3-D Secure exemptions interact directly with this.
  • Multi-provider setups — tokens do not transfer between providers, so a second provider means a second tokenisation of the same card, not a shared reference.

What to store alongside the token

A token on its own is not enough to run a checkout, and the metadata you keep with it is worth deciding deliberately rather than accumulating:

  • Brand and last four digits, so the customer can tell their saved cards apart. Both come back with the token; neither is card data on its own.
  • Expiry month and year, for showing a card as expiring soon. Useful, and it goes stale — the provider’s copy is authoritative.
  • A provider identifier, if you use more than one. A token is meaningless without knowing which provider it belongs to, and that is exactly the field people forget until the second provider arrives.
  • Your own customer reference, not the provider’s, as the primary key. Providers change.

What not to store: anything you would have to protect. If a field would be awkward in a breach notification, the question to ask is why you are holding it rather than how you are encrypting it.

What a token cannot do

  • It cannot be used on the card network directly. It is meaningful only in the context of your account with your provider.
  • It cannot be reversed to a card number by you. The mapping is held elsewhere by design.
  • It cannot be used by another merchant. A token stolen from you is worthless to anyone else, which is the property that makes the whole approach worthwhile.
  • It does not zero your PCI scope.

That last point corrects the most common misconception on this subject. Storing tokens instead of card numbers takes your database out of scope. It does not take out the flow that created the token: the checkout page, every script running on it, and your integration code. Hosted fields on your own page typically map to SAQ A-EP rather than the shorter SAQ A, because the page hosting the iframe can still be attacked — swap the frame, overlay a fake field, or read keystrokes before the real field sees them.

The scope reduction is large and it is not total, and a team that believes otherwise stops doing the things — script inventory, CSP, Subresource Integrity — that the remaining scope requires.

Tokens and the rest of the card data

A token replaces the number. It does not replace the other two credentials, and the rules for those are unchanged: the expiry is stored by the provider alongside the card, and the security code may never be retained after authorisation by anyone, tokenised or not. This is why a saved card cannot ask for its code on a repeat charge — there is nothing stored to compare against, and there was never allowed to be.

Testing tokenised flows

The paths worth exercising deliberately:

  • Tokenisation succeeds and the token is stored against the right customer
  • Tokenisation fails — a declined card at the tokenisation step is a different branch from a declined charge
  • A stored token is charged successfully later
  • A token that has expired or been revoked is handled without an unhandled exception
  • The customer removes a saved card, and the token is deleted at the provider as well as in your database
  • A card is reissued: with a network token the charge still succeeds, with a gateway token it does not — confirm which behaviour you actually have
  • A provider migration, if you ever expect one, is at least rehearsed

Use your provider’s sandbox for all of it — the test card reference collects the published numbers, and the Stripe set documents the tokens that stand in for raw card numbers in server-side tests. Numbers from the generator are the right input for the form layer that sits in front of tokenisation, and the wrong input the moment the request leaves the browser for the provider.

Frequently Asked Questions

A stand-in identifier for a card number. Your system stores the token, your payment provider stores the card, and the token only works against your own account with that provider. If your database is breached, the attacker gets a list of references that cannot be spent anywhere — which is the entire point.
No, and the difference matters for compliance. Encrypted card data is still card data: it can be reversed with the key, and it remains in PCI scope. A token has no mathematical relationship to the number it replaces, cannot be reversed by anyone holding it, and is generally out of scope. Encryption protects data you still hold; tokenisation means not holding it.
A gateway token is issued by your payment provider and only works with that provider, so switching means a migration. A network token is issued by the card network itself — Visa Token Service, Mastercard MDES, or American Express — and survives the card being reissued, because the network keeps it pointed at the current card. That difference is worth real money on a subscription business.
It reduces scope substantially and does not eliminate it. Storing tokens rather than card numbers takes your database out of scope, but the flow that created the token is still in scope — the page your customer typed their card into, the scripts running on it, and your integration with the provider. Hosted fields typically map to SAQ A-EP rather than the shorter SAQ A.
Not by you, and that is deliberate. The mapping lives with the provider or the network, and there is no algorithm that recovers a card number from a token. If you need the last four digits to show a customer which card they saved, the provider returns those separately as metadata — you do not need the number itself for anything a normal checkout does.