Skip to main content
Cresora Commerce
Integration Guides

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).

⚠Warning

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.

  1. The transaction enters THREE_DS_PENDING — one of the 15 TransactionState values. It has a 15-minute TTL.

  2. While THREE_DS_PENDING, TransactionResponse.three_ds is populated — and null in every other state, including after a completion. The ThreeDsChallenge required fields are challenge_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_url is 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.
    • version is the ThreeDsVersion enum: 2.1.0 or 2.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."
  3. The payer completes the challenge at the issuer. Your integration receives the opaque challenge_response token via the redirect from the ACS.

  4. You call the completion endpoint with that token. Idempotency-Key is required, and merchant_id MUST 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_response is opaque and at most 256 characters — pass through exactly what the ACS returned. A merchant_id that does not match the parent transaction's merchant returns 404 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.

  5. The response is a TransactionResponse at 200. A SALE parent transitions to CAPTURED; an AUTHORIZATION parent transitions to AUTHORIZED. A failed authentication transitions to THREE_DS_FAILED and carries three_ds_failure_reason.

Outcomes and failure reasons

three_ds_failure_reason is populated only on THREE_DS_FAILED. The exact values:

three_ds_failure_reasonWhat happenedWhat to do
challenge_failedThe payer attempted authentication and the issuer did not accept itMay be retried by creating a new transaction and a fresh authentication session
challenge_abandonedThe payer left the ACS page without finishingMay be retried by creating a new transaction and a fresh authentication session
callback_timeoutThe 15-minute TTL elapsed with no completion; written by the timeout sweepTreat as unauthenticated. Create a new transaction if the payer is still present
signature_invalidThe authentication result did not validateDo not retry the same session. Investigate before re-presenting
unsupported_cardThe card cannot be authenticated through this flowDo not retry the same card through this flow
issuer_declinedThe issuer declined the authenticationDo not retry. Ask the payer for another payment method
ℹNote

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/complete against a transaction already CAPTURED, AUTHORIZED, or THREE_DS_FAILED returns the current state as 200 without 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_FAILED with three_ds_failure_reason of callback_timeout.
  • A late completion against a swept transaction returns 200 with THREE_DS_FAILED — not 422. Read the state from the body, never from the status code alone.
  • 422 means the transaction never reached 3DS at all — for example INITIATED, VOIDED, or REFUNDED.

Webhooks

EventFires when
three_ds.challenge_issuedA transaction enters THREE_DS_PENDING and a challenge is available
three_ds.completedAuthentication succeeded and the parent transaction reached CAPTURED or AUTHORIZED
three_ds.failedThe 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_PENDING as 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_ds being null everywhere except THREE_DS_PENDING, including on the completion response itself. Never dereference it unconditionally.
  • Read outcomes from state, not the HTTP status — 200 covers success, the replay no-op, and the late-completion failure.
  • Send Idempotency-Key on the completion call and let a repeat return the current state, rather than keeping your own "already handled" bookkeeping.
🔒Compliance

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.

💡Tip

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.