> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Tokenization & Card Payments FAQ

> Answers to the most common questions about Waffo card tokenization: what a token is, how to obtain one, the token lifecycle, CIT vs MIT, first payment vs subsequent charges, and handling expired or invalid tokens.

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](/docs/api-reference/introduction)
and the
[Acquiring API Specification](/docs/api-reference/introduction).

## 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:

| Status | Meaning |
| - | - |
| `UNVERIFIED` | Card details submitted, but no successful payment has been made with the token yet. It can still be charged as CIT. |
| `VERIFIED` | The card has been verified. |
| `EXPIRED` | The token has expired. |
| `SUSPENDED` | Usage has been suspended. |

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](/docs/en/developer-docs/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:

| What | Validity |
| - | - |
| `tokenSessionId` returned by `/api/v1/tokenization/generate` | **Short-lived** (a fixed platform window from creation) and single-use — once a card has been submitted against it, the session is spent. Open it when the customer is about to enter the card, not in advance |
| `tokenId` for a card | the **earlier** of: a fixed multi-year platform validity period counted from when the token was created, or the last moment of the card's own expiry month |

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 `tokenId`s?

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 `merchantUserId`s that
  bind the same physical card receive two different `tokenId`s**. 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:

| Repeated call | Behaviour |
| - | - |
| `/api/v1/tokenization/generate` with a `tokenRequestId` you already used | Returns the data of the existing session instead of opening a new one — safe to retry after a timeout, and it does not consume a second session. |
| Submitting a card against a `tokenSessionId` that already carries a token | The same result is returned when it is the same card. A **different** card against the same session is rejected as an idempotency conflict — use one fresh session per card. |

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

| Mode | What it is | How to send it |
| - | - | - |
| CIT (Customer Initiated Transaction) | The customer actively takes part in the payment — for example, paying at checkout with a saved card. | Call `/api/v1/order/create` with the token in `userPaymentAccessToken`; do not set `merchantInitiatedMode`. |
| MIT (Merchant Initiated Transaction) | You charge the user **without customer involvement** — for example, a renewal or usage-based deduction you schedule yourself. | Call `/api/v1/order/create` with `userPaymentAccessToken` **and** `merchantInitiatedMode` set to `scheduled` or `unscheduled`. |

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.

| | CIT with a saved token | MIT |
| - | - | - |
| Hosted checkout | Used — the user completes the action from `orderAction` | Never; an MIT order is deliberately blocked from reaching checkout |
| Create response | Commonly `AUTHORIZATION_REQUIRED` plus `orderAction` | Charge attempt runs server-side; no user step and no checkout URL |
| CVV re-entry | Normally asked for on the hosted page | Not asked |
| `threeDsDecision` | Honoured for card / ApplePay / GooglePay | Ignored |
| Retry after a failure | Where user retry is enabled for your account, the hosted page can offer the user another attempt on the same order | Not retried on the same order — create a new order to try again |

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:

| Action | Effect |
| - | - |
| `SET_DEFAULT` | Makes that card the user's default. There is at most one default per user, so the previous default is replaced. |
| `SUSPEND` | Suspends the saved card so it is no longer offered. |

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](/docs/en/support/subscription-lifecycle-management).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.