3-D Secure (3DS2)
How 3DS2 card authentication works on the Cresora platform — where challenges are actually issued today, and the reserved THREE_DS_PENDING state and completion endpoint contract.
3-D Secure 2 (3DS2) is the card-network protocol that lets an issuer authenticate the cardholder before a payment is authorized. When authentication succeeds, liability for a fraudulent chargeback generally shifts from the merchant to the issuer, and where Strong Customer Authentication (SCA) mandates apply it is what makes a card payment acceptable at all. Authentication itself happens at the issuer's Access Control Server (ACS).
Read this before you build anything against 3DS on Cresora. No current
POST /transactions create path issues a step-up. From the published spec, verbatim:
"No current POST /transactions create path issues a step-up — the saved-card SALE charges
an already-authenticated vault credential; this state and the completion endpoint are
retained for reserved card-authentication flows." Three consequences:
- A direct-API SALE against a vault token will not produce a 3DS challenge today.
- Card authentication happens where the card is actually entered — on the hosted page. See Hosted Payment Page.
- There is no request field to turn 3DS on. No property exists on any request to enable or force a challenge.
The THREE_DS_PENDING state and completion endpoint below are the platform's reserved
card-authentication surface: understand them and make your handlers tolerate them, but you
cannot force a step-up through any current create call.
The reserved completion flow
This is the contracted behavior for a transaction in a 3DS challenge, documented so integrations are correct on the day challenge-issuing flows ship.
-
The transaction enters
THREE_DS_PENDING— one of the 15TransactionStatevalues. It has a 15-minute TTL. -
While
THREE_DS_PENDING,TransactionResponse.three_dsis populated — andnullin every other state, including after a completion. TheThreeDsChallengerequired fields arechallenge_url,acs_transaction_id,version,expires_at.{ "transaction_id": "0190d8a1-2b3c-7d4e-9f12-7a8b9c0d1e2f", "state": "THREE_DS_PENDING", "three_ds": { "challenge_url": "https://acs.issuer.example/challenge/abc123", "acs_transaction_id": "…", "version": "2.2.0", "expires_at": "…" } }challenge_urlis issuer-hosted — it is the ACS page. It is not a Cresora page, and the platform does not host a payment or challenge domain for this flow.versionis theThreeDsVersionenum:2.1.0or2.2.0. Per spec: "Cresora always attempts 2.2.0 first and falls back to 2.1.0… 3DS 1.0 is deprecated by card networks and never produced by the platform."
-
The payer completes the challenge at the issuer. Your integration receives the opaque
challenge_responsetoken via the redirect from the ACS. -
You call the completion endpoint with that token.
Idempotency-Keyis required, andmerchant_idMUST match the parent transaction's merchant.curl curl -X POST "https://api.cresoracommerce.ai/api/v1/transactions/0190d8a1-2b3c-7d4e-9f12-7a8b9c0d1e2f/three-ds/complete" \ -H "Authorization: Bearer csk_ab12cd34_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: idem_$(uuidgen)" \ -d '{ "challenge_response": "cres_3ds_pARf8UXu0Wm9qQPDfT4bP0Vk", "merchant_id": "0190d8a1-9f12-7d4e-2b3c-1e2f7a8b9c0d" }'challenge_responseis opaque and at most 256 characters — pass through exactly what the ACS returned. Amerchant_idthat does not match the parent transaction's merchant returns404 not_found— deliberately the same response as for a transaction that does not exist, so the endpoint cannot be used to probe which transaction ids exist under another merchant. -
The response is a
TransactionResponseat200. ASALEparent transitions toCAPTURED; anAUTHORIZATIONparent transitions toAUTHORIZED. A failed authentication transitions toTHREE_DS_FAILEDand carriesthree_ds_failure_reason.
Outcomes and failure reasons
three_ds_failure_reason is populated only on THREE_DS_FAILED. The exact values:
three_ds_failure_reason | What happened | What to do |
|---|---|---|
challenge_failed | The payer attempted authentication and the issuer did not accept it | May be retried by creating a new transaction and a fresh authentication session |
challenge_abandoned | The payer left the ACS page without finishing | May be retried by creating a new transaction and a fresh authentication session |
callback_timeout | The 15-minute TTL elapsed with no completion; written by the timeout sweep | Treat as unauthenticated. Create a new transaction if the payer is still present |
signature_invalid | The authentication result did not validate | Do not retry the same session. Investigate before re-presenting |
unsupported_card | The card cannot be authenticated through this flow | Do not retry the same card through this flow |
issuer_declined | The issuer declined the authentication | Do not retry. Ask the payer for another payment method |
Retrying always means creating a new transaction. There is no published retry window, backoff, or attempt allowance for 3DS here — do not build one against an assumed value.
Replay and TTL semantics
- Completion is replay-safe by contract. Calling
three-ds/completeagainst a transaction alreadyCAPTURED,AUTHORIZED, orTHREE_DS_FAILEDreturns the current state as200without re-calling the gateway — a successful no-op, so a duplicate ACS redirect or a client retry cannot double-charge. - The TTL is 15 minutes. A sweep transitions expired challenges to
THREE_DS_FAILEDwiththree_ds_failure_reasonofcallback_timeout. - A late completion against a swept transaction returns
200withTHREE_DS_FAILED— not422. Read the state from the body, never from the status code alone. 422means the transaction never reached 3DS at all — for exampleINITIATED,VOIDED, orREFUNDED.
Webhooks
| Event | Fires when |
|---|---|
three_ds.challenge_issued | A transaction enters THREE_DS_PENDING and a challenge is available |
three_ds.completed | Authentication succeeded and the parent transaction reached CAPTURED or AUTHORIZED |
three_ds.failed | The transaction reached THREE_DS_FAILED, including via the timeout sweep |
Use these exact event names — platform prose elsewhere spells them two other ways, and the event enum above is authoritative.
Building tolerant handlers
No create path issues a step-up today, but the state and endpoint are contracted — so build for both:
- Treat
THREE_DS_PENDINGas a possible state on any transaction read. Do not model transaction state as a closed set that excludes it, and do not treat it as an error. - Handle
three_dsbeingnulleverywhere exceptTHREE_DS_PENDING, including on the completion response itself. Never dereference it unconditionally. - Read outcomes from
state, not the HTTP status —200covers success, the replay no-op, and the late-completion failure. - Send
Idempotency-Keyon the completion call and let a repeat return the current state, rather than keeping your own "already handled" bookkeeping.
CAVV, xid, and eci are held server-side for chargeback defence and are never
returned on the transaction projection. Do not build a flow that expects to read them.
Sandbox testing. There is no confirmed sandbox trigger surface for the reserved 3DS flow — nothing in the published contract produces a challenge on demand, so do not build test fixtures around an assumed trigger. Testing guidance will accompany the flows that surface challenges when they ship; for general sandbox behavior see Testing.