External 3DS
Charge with a 3-D Secure result you obtained from your own provider — one call, no challenge.
If you authenticate the cardholder with your own 3DS provider before charging, you already
hold the result: the cryptogram, the ECI and the Directory Server transaction ID. External 3DS
lets you pass that result to Tonder inside the /process/ request you already send. Tonder does
not run its own 3DS — it forwards your authentication data to the acquirer and processes the
payment.
In short: you authenticate → you send us the result → we charge with it.
Available on the Direct API only (POST /process/). The SDKs and Hosted Checkout run 3DS for
you, so there is nothing to pass.
Prerequisites
External 3DS is off by default. Ask your Tonder representative to enable it on your account.
If it is not enabled and you send threeDomainSecure, the request is rejected with a clear error.
- Your existing Direct API integration — same endpoint, same
Authorization: Token <API_KEY>, same tokenized card. No new endpoint or credentials. - A completed 3DS authentication from your provider, from which you have the cryptogram (CAVV or UCAF), the ECI and — for Mastercard — the Directory Server transaction ID.
What changes in the request
One addition: a top-level threeDomainSecure object carrying your result. We also strongly
recommend card_brand inside payment_method (see below). Everything else stays exactly as it is.
threeDomainSecure
| Field | Required | Description |
|---|---|---|
eci | Yes | Electronic Commerce Indicator from your 3DS result. |
cavv | Visa | Base64 authentication cryptogram (Visa). |
ucaf | Mastercard | Base64 authentication cryptogram / AAV (Mastercard). Same value family as cavv. |
directoryServerTransactionID | Mastercard | Directory Server transaction ID from your 3DS result. |
specificationVersion | No | EMVCo protocol version. Defaults to 2.2.0. |
collectionIndicator | No (Mastercard) | Mastercard collection indicator. Derived from eci if omitted. |
acceptRisk | Conditional | Must be true when eci indicates not authenticated (Mastercard 00 / Visa 07) — you accept liability. |
Where each thing goes
Three things live at three different levels. Getting this wrong is the most common integration mistake:
| Item | Where it goes |
|---|---|
threeDomainSecure (the whole object) | Top level of the request body |
eci, cavv, ucaf, directoryServerTransactionID, acceptRisk, … | Inside threeDomainSecure |
card_brand | Inside payment_method |
If you nest threeDomainSecure inside payment_method, it is ignored: the payment does not use
your authentication data and Tonder runs its own 3DS instead. It must be at the top level.
ECI values and rules
| Network | ECI | Meaning |
|---|---|---|
| Visa | 05 | Authenticated |
| Visa | 06 | Attempted |
| Visa | 07 | Not authenticated — requires acceptRisk: true |
| Mastercard | 02 | Authenticated |
| Mastercard | 01 | Attempted |
| Mastercard | 00 | Not authenticated — requires acceptRisk: true |
Cryptogram format. cavv / ucaf must be the Base64-encoded cryptogram your provider
returned, not hex. A hex string is rejected.
card_brand — recommended. Tonder receives a tokenized card, not the PAN, so it cannot always
determine the network on its own. You just authenticated the card, so you know it — send it:
- With
card_brand: Tonder validatesthreeDomainSecureagainst the network up front and answers a clear400if something is inconsistent (a Visa ECI on a Mastercard card, say). - Without it: validation is lighter and an inconsistency is caught later by the acquirer, as a
decline.
card_brandgets you faster, clearer errors.
Accepted values: "visa", "mastercard" (case-insensitive).
Examples
Mastercard
curl -X POST "https://stage.tonder.io/api/v1/process/" \
-H "Authorization: Token YOUR_TONDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operation_type": "payment",
"amount": 150.00,
"currency": "MXN",
"customer": {
"name": "Jane Doe",
"email": "jane@testuser.com"
},
"payment_method": {
"type": "CARD",
"card_number": "9230-0892-4469-1474",
"cardholder_name": "c05d89b2-299c-4f93-b49a-42be00d3b64b",
"cvv": "d31f0da3-0ed3-4ad8-8b68-14c2669a99a7",
"expiration_month": "e401a32e-4174-424f-9688-727005f6a80e",
"expiration_year": "bd9ccc23-3d00-4109-9626-fc6581389063",
"card_brand": "mastercard"
},
"threeDomainSecure": {
"ucaf": "kCO52e75N318oAB3MPZU6EhB2Td6",
"eci": "02",
"directoryServerTransactionID": "9e8c4ff6-23d9-4fba-8446-6e8e9d13f42e",
"specificationVersion": "2.2.0",
"collectionIndicator": "2"
},
"client_reference": "ORD-001",
"return_url": "https://tonder.io"
}'Visa
Same request, with cavv instead of ucaf and no Directory Server transaction ID:
{
"operation_type": "payment",
"amount": 150.00,
"currency": "MXN",
"customer": { "name": "Jane Doe", "email": "jane@testuser.com" },
"payment_method": {
"type": "CARD",
"card_number": "9230-0892-4469-1474",
"cardholder_name": "c05d89b2-299c-4f93-b49a-42be00d3b64b",
"cvv": "d31f0da3-0ed3-4ad8-8b68-14c2669a99a7",
"expiration_month": "e401a32e-4174-424f-9688-727005f6a80e",
"expiration_year": "bd9ccc23-3d00-4109-9626-fc6581389063",
"card_brand": "visa"
},
"threeDomainSecure": {
"cavv": "AAABBoVBaZKAR3BkdkFpELpWIiE=",
"eci": "05",
"specificationVersion": "2.2.0"
},
"client_reference": "ORD-002",
"return_url": "https://tonder.io"
}Not-authenticated ECI (acceptRisk)
When eci is 00 (Mastercard) or 07 (Visa) you must accept the liability explicitly, with
acceptRisk: true inside threeDomainSecure:
"threeDomainSecure": {
"ucaf": "kCO52e75N318oAB3MPZU6EhB2Td6",
"eci": "00",
"directoryServerTransactionID": "9e8c4ff6-23d9-4fba-8446-6e8e9d13f42e",
"specificationVersion": "2.2.0",
"collectionIndicator": "2",
"acceptRisk": true
}What to expect in the response
- A successful charge comes back approved in a single call, with no challenge step — you already authenticated the cardholder, so Tonder does not initiate its own 3DS.
- A
Pendingresponse with anext_action(a challenge) means your authentication data was not applied. The usual causes:threeDomainSecurenested insidepayment_methodinstead of the top level; your account is not enabled for External 3DS; the object was missing or empty.
As with any payment, verify the final status at GET /api/v1/transactions/{id}/ — it's the
source of truth.
Common errors
| Response | Cause | Fix |
|---|---|---|
4xx "not enabled for this business" | External 3DS is not enabled on your account | Ask Tonder to enable it |
Pending / challenge instead of a direct charge | threeDomainSecure nested in payment_method, or empty | Move it to the top level; make sure it has values |
400 "eci … is not valid for {brand}" | The ECI does not match the card network | Send the correct ECI for the network |
400 "ucaf/directoryServerTransactionID is required" | Missing Mastercard fields | Include both ucaf and directoryServerTransactionID |
400 "cavv is required" | Missing Visa cryptogram | Include cavv |
400 "requires acceptRisk=true" | Not-authenticated ECI (00/07) without acceptance | Add acceptRisk: true inside threeDomainSecure |
400 "must be a base64-encoded cryptogram" | Cryptogram sent as hex | Send the Base64 value from your provider |
Declined at the acquirer (200, declined) | Network/ECI mismatch not caught up front | Send card_brand so Tonder validates before the acquirer |
