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:- Call
/api/v1/tokenization/generatewith a uniquetokenRequestId(idempotency key), yourmerchantUserId,tokenType: "CARD", and anotifyUrl. The response returns atokenSessionId. - On your frontend, initialize the Waffo JavaScript SDK
(
@waffo/payment-sdk) and calltokenizationSubmit(tokenSessionId, tokenData). The SDK encrypts the card data before sending it to Waffo. CardexpiryacceptsMM/YYYY(andMM/YYon input), but queries and notifications always returnMM/YYYY. - Waffo pushes a
TOKENIZATION_NOTIFICATIONwebhook to yournotifyUrlcarrying thetokenId,tokenStatus, and masked card details (maskedCardInfo,cardBin,cardScheme,cardType, and so on). - Use
/api/v1/tokenization/inquiryto query token details at any time — bytokenId(returns one entry) or bymerchantUserId(may return several).
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/inquiryresponse. What you get back istokenStatusplus the cardexpiryinside the token data — enough to derive the rule above yourself if you want to warn users before a renewal fails. tokenStatusis moved toEXPIREDby 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. merchantUserIdis part of the key, so two differentmerchantUserIds that bind the same physical card receive two differenttokenIds. 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 freshtokenId.
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
threeDsDecisionfield (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?
- Bind the card and obtain a
tokenId(see above). - Wait for the token to reach a chargeable state — watch
TOKENIZATION_NOTIFICATIONor poll/api/v1/tokenization/inquiry. - For payments the user takes part in, create a CIT order with
userPaymentAccessToken. - For later charges you initiate yourself, create MIT orders with
merchantInitiatedMode(scheduledfor a fixed cadence you manage,unscheduledfor 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:
- Parse
orderActionand readactionType—WEBmeans redirect the user towebUrl,DEEPLINKmeans redirect todeeplinkUrl. - 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.
- Treat
AUTHORIZATION_REQUIREDas non-final and wait for the payment notification (or poll order inquiry) for the outcome.
/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) orA0023(order can not be created due to expired token) on order creation: the stored token is no longer usable. Call/api/v1/tokenization/removeto unbind it (bytokenId, or all of a user’s tokens bymerchantUserId), then ask the user to bind a new card.B1002(card has expired) orB1001(invalid card information) during binding: the card itself is the problem — the user must provide a valid card.B1005(token session ID is expired): thetokenSessionIdfromgenerateis no longer valid — call/api/v1/tokenization/generateagain.B1003(card binding limit exceeded) andB1004(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
notifyUrlkeeps 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/removeto unbind it before asking the user for new card credentials.