Skip to main content
This page answers the questions merchants ask most often about card binding and token-based payments. Field-level contracts live in the Tokenization API Specification and the Acquiring API Specification.

What is card tokenization, and what is a tokenId?

Tokenization replaces a user’s raw card details with a reusable tokenId. Store the tokenId securely on your side; when initiating user payments or backend deductions, use it in place of the card information. The supported tokenType is CARD. Once you hold a token, you can use it for one-time payments and for subscription-style charges you manage yourself, on a scheduled or unscheduled basis.

How do I bind a card and get a token?

The standard flow keeps raw card data out of your servers:
  1. Call /api/v1/tokenization/generate with a unique tokenRequestId (idempotency key), your merchantUserId, tokenType: "CARD", and a notifyUrl. The response returns a tokenSessionId.
  2. On your frontend, initialize the Waffo JavaScript SDK (@waffo/payment-sdk) and call tokenizationSubmit(tokenSessionId, tokenData). The SDK encrypts the card data before sending it to Waffo. Card expiry accepts MM/YYYY (and MM/YY on input), but queries and notifications always return MM/YYYY.
  3. Waffo pushes a TOKENIZATION_NOTIFICATION webhook to your notifyUrl carrying the tokenId, tokenStatus, and masked card details (maskedCardInfo, cardBin, cardScheme, cardType, and so on).
  4. Use /api/v1/tokenization/inquiry to query token details at any time — by tokenId (returns one entry) or by merchantUserId (may return several).
If your organization holds the necessary PCI-DSS certification to handle raw cardholder data, you may instead pass pan, name, expiry, and cvv directly in the tokenData object of /api/v1/tokenization/generate; in that case the tokenId is returned synchronously in the API response.

What does the token lifecycle look like?

tokenStatus has four values: You receive a TOKENIZATION_NOTIFICATION whenever the token status changes or the token data is updated. Respond with success after processing; if Waffo receives failed or unknown, Waffo retries the notification on a back-off schedule spread over the following day; see Webhook retry for the schedule.

How long do a tokenSessionId and a tokenId stay valid?

Two different clocks, and only one of them is a fixed period: So a card that expires soon yields a token that stops being usable at the end of its expiry month even if it was bound today, while a card expiring far in the future yields a token that runs the full platform validity period. Two practical consequences:
  • The token’s own expiry timestamp is not part of the /api/v1/tokenization/inquiry response. What you get back is tokenStatus plus the card expiry inside the token data — enough to derive the rule above yourself if you want to warn users before a renewal fails.
  • tokenStatus is moved to EXPIRED by a periodic sweep, not at the exact second the token lapses, so a token can sit slightly past its computed expiry while still reading as usable. Schedule against the expiry rule; treat the status and the order-creation error codes as the final word.

If the same user binds the same card twice, do I get two tokenIds?

No. Card tokens are de-duplicated on re-binding: you get the existing tokenId back when all of the following match a token that has not been removed — your merchantId (plus subMerchantId, where you send one), the same merchantUserId, the same card, the same cardholder name, and the same tokenType. Worth designing around:
  • You do not need to de-duplicate the saved-card list yourself; do key your own storage on tokenId, because a second binding returns the first token rather than a new one.
  • merchantUserId is part of the key, so two different merchantUserIds that bind the same physical card receive two different tokenIds. There is no cross-user card identifier exposed for you to join on.
  • A different cardholder name on the same card is treated as a different credential and produces a new token.
  • After /api/v1/tokenization/remove, the token is excluded from queries and is never reused — binding that card again issues a fresh tokenId.
Both binding calls are idempotent, in different ways:

What is the difference between CIT and MIT, and when do I use each?

Two MIT-specific rules to note:
  • CIT works with a freshly created token — the first payment is what verifies the card, so you do not need to wait for a “verified” state.
  • MIT requires an already-usable stored credential. If the token has never completed a successful payment (or its stored credential is no longer valid), the MIT charge is rejected with A0045. In practice: run the first charge as CIT, then use MIT for later charges.
  • The threeDsDecision field (3DS_FORCE / NO_3DS) is only effective for card / ApplePay / GooglePay CIT payments; it is ignored for MIT.

How do the first payment and subsequent charges fit together?

  1. Bind the card and obtain a tokenId (see above).
  2. Wait for the token to reach a chargeable state — watch TOKENIZATION_NOTIFICATION or poll /api/v1/tokenization/inquiry.
  3. For payments the user takes part in, create a CIT order with userPaymentAccessToken.
  4. For later charges you initiate yourself, create MIT orders with merchantInitiatedMode (scheduled for a fixed cadence you manage, unscheduled for event-driven deductions).

Why did /order/create return AUTHORIZATION_REQUIRED instead of charging my saved token?

Because a CIT against a saved token is not a server-side charge. When you send userPaymentAccessToken without merchantInitiatedMode, Waffo creates the order and hands it back with orderStatus: AUTHORIZATION_REQUIRED and a populated orderAction. No money has moved yet; the cardholder still has a step to complete. This is the normal outcome of a one-click CIT, not an error. What to do with it:
  1. Parse orderAction and read actionType — WEB means redirect the user to webUrl, DEEPLINK means redirect to deeplinkUrl.
  2. On Waffo’s hosted checkout the saved card is already selected and shown masked; the CVV is normally the only field the user has to fill in. A 3DS challenge can follow.
  3. Treat AUTHORIZATION_REQUIRED as non-final and wait for the payment notification (or poll order inquiry) for the outcome.
Never branch on “did /order/create return success” alone — branch on orderStatus, and whenever orderAction is present, assume a user step is outstanding. If you need a charge with no customer present, that is exactly what merchantInitiatedMode is for; see the CIT/MIT section above.

The token expired or a payment was rejected. What should I do?

  • A0037 (user payment token is invalid / has expired) or A0023 (order can not be created due to expired token) on order creation: the stored token is no longer usable. Call /api/v1/tokenization/remove to unbind it (by tokenId, or all of a user’s tokens by merchantUserId), then ask the user to bind a new card.
  • B1002 (card has expired) or B1001 (invalid card information) during binding: the card itself is the problem — the user must provide a valid card.
  • B1005 (token session ID is expired): the tokenSessionId from generate is no longer valid — call /api/v1/tokenization/generate again.
  • B1003 (card binding limit exceeded) and B1004 (card scheme not supported by current merchant contract): these depend on your contracted configuration — check the Merchant Portal or contact integration support.

I bind the card during a payment. Where is the tokenization result notified?

Configure a dedicated tokenization notification URL against your API key in the Merchant Portal (Settings → Integration). Waffo snapshots it onto each order created with that key, and card-binding results are notified there.
  • It is a separate channel: your payment-result notifyUrl keeps receiving payment notifications as before.
  • It is not an order-creation parameter — you cannot override it per order.
  • If it is not configured, tokenization notification behavior is unchanged.

Can I set a user’s default card?

Yes, through a dedicated endpoint rather than on order creation. Call /api/v1/saved-payment-methods/config with the token and the action you want: The default card is used for ordering the saved-card list the customer sees at checkout — expired cards are pushed to the end of the list.

The user’s card was renewed by the bank. Is the stored token still usable?

Do not assume it is. Waffo does not currently refresh stored cards from issuer-side updates on its own, so plan for re-binding:
  • Watch for tokenization and payment notifications that report a token as no longer usable, and prompt the user to bind a card again.
  • If the card is unlinked on the wallet or issuer side, the stored credential becomes unusable and cannot be reactivated.
  • An expired token can no longer be charged, but you can still call /api/v1/tokenization/remove to unbind it before asking the user for new card credentials.
For subscriptions, keeping a backup payment method on the subscription is the practical safeguard — see Subscription lifecycle.