Is tokenType always CARD?
No. CARD is the value most integrations use and the only one the public
specification documents, but tokenType is a general field: the token service
maintains a set of types covering non-card instruments as well, and
wallet-class tokens in particular follow different rules for required fields
and for expiry (both below).
What you should do with that:
- Do not hardcode
"CARD"as the only value your code can produce or parse. Read thetokenTypeback from inquiry responses and notifications, and handle an unfamiliar value by storing it rather than failing. - Do not assume a type is available to you. Which types your account can actually tokenize follows your payment-method configuration. An unsupported type is rejected at generate time as a parameter error.
- Ask your Waffo integration contact which types apply to the payment methods you have, rather than probing the API.
What does a wallet-class token need that a card token does not?
Three extra fields inside the token data, all mandatory:
Miss any of them and generate fails as
A0003 Parameter validation failed.
The orderId requirement is the structural difference worth understanding: a
card can be tokenized standalone, whereas a wallet binding is something the user
authorises in the course of paying. So the wallet token is created against a
real order, not in a vacuum. In practice you request it as part of a payment
rather than as a separate “add a payment method” step.
How long does a wallet token last?
So a wallet token has no equivalent of the card-expiry ceiling — there is no
underlying expiry date to respect — and the expiry lands on a day boundary
rather than a month boundary.
Two things follow:
- Do not derive a wallet token’s remaining life from card logic. There is no
expiryfield in the token data to reason about. - The token can still stop working long before its calculated expiry, because
the user unlinked the authorisation on the wallet side. That is invisible to
you until a charge fails. Handle
A0037(the user payment token is invalid or has expired) as a prompt to re-authorise, not as a transient error.
If the same user authorises the same wallet again, do I get the same token?
Not necessarily — and this is where wallet tokens differ from cards most sharply. The re-use key for a wallet-class token includes the order the binding happened on, alongside your merchant ID (and sub-merchant ID where you send one), yourmerchantUserId, the token type and the payment method.
Because a new authorisation happens on a new order, a second binding of the same
wallet by the same user generally produces a new tokenId. Card tokens
behave differently: their key is based on the credential itself, so re-binding
the same card returns the existing token (see the card FAQ).
Design consequences:
- Key your storage on
tokenId, and expect a user to accumulate more than one wallet token over time. - Show the user a manageable list rather than a growing pile: suspend or unbind the ones they no longer want (last section on this page).
- Do not use the presence of a token as proof the user has exactly one wallet binding.
What must I send on inquiry and remove for a non-card token?
Both endpoints have a conditional shape rather than one required identifier:
The practical read:
tokenType is not decoration on these two calls. If you
have built your integration around cards and pass no tokenType, adding a
second token class will break your lookups in a way that looks like “the token
does not exist”.
All three token endpoints also assert that the merchantId in the body matches
the merchant ID your API key is issued for, and reject a mismatch as A0003
with the detail mid in params is inconsistent with apiKey.
Why was my tokenization submit rejected?
Beyond signing and field-shape problems, there are three business reasons a card submission is refused. All three are decided before anything is stored, and all three are permanent for that input — retrying the identical data will fail identically.
Two details that surprise people:
- The checksum digit is not a gate. A card number that fails the standard checksum is logged as a warning and processing continues — the authoritative answer comes from the issuing-range lookup. So do not expect a checksum rejection, and do keep your own client-side checksum check to catch typos early for the user’s benefit.
MM/YYis accepted on input by the collection SDK, but queries and notifications always returnMM/YYYY. Store the four-digit form.
A0029
(card data invalid), A0030 (card issuing range not found), A0032 (payment
token data invalid), A0023 (order cannot be created due to expired token).
Why is the cardholder name rejected?
Because it contains characters outside the Latin alphabet. The rule is strict: the cardholder name must contain only English letters. A name with accented characters, or in a non-Latin script, is refused with the failure reasonINVALID_CARDHOLDER_NAME and the message Cardholder name must contain only
English letters, surfacing as A0029 on the API.
How to handle it well:
- Transliterate at the point of collection. Ask for the name “as printed on the card”; cards are embossed in Latin characters, so the correct value almost always exists.
- Validate in your own form with a clear message, rather than letting the user discover it at payment.
- Remember the name is part of a card token’s re-use key: the same card submitted with a differently spelled name is treated as a different credential and gets its own token.
What does an MIT charge do differently from a CIT charge?
The request-level difference (merchantInitiatedMode, and the A0045
requirement for an already-usable credential) is on the
card tokenization page. The behavioural
differences matter just as much:
The single sentence to take away: an MIT charge has no user interface and no
user. Anything your flow relies on the checkout page for — a redirect, a
consent step, a field the user fills in — does not exist here.
My MIT charge failed. Do I just retry it?
Not blindly, for three separate reasons:- There is no user-side retry. A CIT failure can end with the user choosing another method on the checkout page. An MIT failure just ends.
- Some failures are permanent for that credential.
A0045(the stored credential is not in a usable state) andA0037(the token is invalid or expired) will not become successful on a retry. The fix is a fresh customer-initiated authorisation. - Retries are themselves rate-limited by card-scheme rules — see the next question.
Why was a card payment refused before it was even attempted?
Because card schemes impose compliance limits on how many times the same card payment may be retried, and Waffo enforces them locally. When an attempt would breach one, it is rejected by the system rather than being forwarded — which is why the failure can come back faster than a normal decline, and why nothing appears on the card statement. What you need to know about the counting:- It is counted per card scheme, and separately again according to the scheme’s own classification of the merchant. Two cards from different schemes on the same order are not pooled.
- Waffo does not publish the thresholds, and you should not attempt to reverse-engineer them: they are set by the schemes and can change without anything changing on your account.
- The failure surfaces as the failure reason
CARD_PAYMENT_RETRY_LIMIT, mapped toC0005on the API.
- Never auto-retry a declined card payment immediately. It burns the allowance without any chance of a different answer.
- Change something between attempts — a different payment method, a re-entered card, a later schedule — or ask the user.
- If you are hitting this regularly, the problem is upstream: too many retries against cards that will not authorise.
Can my users manage their saved payment methods themselves?
Yes, through one endpoint rather than through order creation:POST /api/v1/saved-payment-methods/config, which takes your merchantId, the
merchantUserId, the saved-method token, and an action:
Together with
/api/v1/tokenization/remove (unbind entirely) that is enough to
build a complete “manage my payment methods” screen: list, set default, hide,
delete. What each of those does to the checkout ordering is on the
card tokenization page.
Two boundaries worth knowing before you design the screen:
- The endpoint is in the payin scope of an API key, so it is a backend call — never expose it to a browser or a mobile client directly.
- Removal is final: a removed token is excluded from lookups and is never reissued. Suspension is the reversible-looking option; treat removal as permanent.
Which of these belong on which page?
Still not sure why a specific binding or charge was refused? Contact
support with the
tokenRequestId or the order ID —
the failure reason is recorded against both.