SettleFlow API

3D Secure

Handle the 3DS challenge redirect and resume the flow after the customer returns

Overview

3D Secure (3DS2 / EMV 3DS) is required for most European card payments under PSD2 Strong Customer Authentication rules. The gateway handles the orchestration with the acquirer — your integration only needs to handle the browser redirect and the status check after the customer comes back.

When does 3DS trigger?

  • When the card's issuer requires it (the common case for European cards).
  • When you force it explicitly by sending "3DS": "yes" on the payment request.
  • When your account is configured as 3DS-only — in that case payments without 3DS=yes fail with error code 15.

Some combinations are mandatory:

ScenarioBehaviour
Maestro cardAlways requires 3DS. Missing 3DS=yes returns code 22.
Account not allowed to use 3DS3DS=yes rejected with code 8.
3DS=yes sent without ReturnUrlRejected with code 106.

Flow

 ┌────────────┐   1. POST /v1/payment/direct   ┌─────────────┐
 │  Merchant  │ ─────────────────────────────▶ │  Gateway    │
 │   server   │                                 │             │
 │            │ ◀───── { 3DSecureUrl } ──────── │             │
 └────────────┘                                 └─────────────┘

       │ 2. Return 3DSecureUrl to the browser

 ┌────────────┐   3. Browser redirected        ┌─────────────┐
 │  Customer  │ ─────────────────────────────▶ │  Gateway    │
 │  browser   │                                 │  hand-off   │
 │            │       hands the customer on ──▶ │    page     │
 └────────────┘                                 └─────────────┘
       │                                              │
       │                                              ▼
       │                                        ┌─────────────┐
       │        4. 3DS challenge                │ Card issuer │
       │ ◀───────────────────────────────────── │   ACS page  │
       ▼                                        └─────────────┘

       │ 5. Customer redirected back

 ┌────────────┐   6. POST /v1/status    ┌─────────────┐
 │  Merchant  │ ─────────────────────────────▶ │  Gateway    │
 │   server   │ ◀────── { Status: ... } ────── │             │
 └────────────┘                                 └─────────────┘

       │ 7. Settleflow also fires a webhook
       ▼  asynchronously to your configured endpoint

3DSecureUrl is always a URL on the API host — never the card issuer's or the provider's own address. The customer lands on a short hand-off page that passes them to the challenge. Follow the URL as-is: do not parse it, store it, or allow-list a destination domain from it. It is single-purpose and short-lived (about an hour), so mint a new payment rather than reusing an old link.

Step-by-step

1. Request the payment with 3DS enabled

curl -X POST https://api.sandbox.settleflow.io/v1/payment/direct \
  -H "epro-api-key: pk_test_..." \
  -d '{
    "Amount": "4999",
    "Uid": "customer-42",
    "Tid": "order-2026-001",
    "Email": "jane@example.com",
    "CardNumber": "4000000000003220",
    "CardMonth": "12",
    "CardYear": "2028",
    "CardCVV": "123",
    "3DS": "yes",
    "ReturnUrl": "https://your-shop.com/payment/return"
  }'

2. Inspect the response

{
  "Code": 0,
  "Result": {
    "OperationType": "payment",
    "Status": "pending",
    "Tid": "order-2026-001",
    "Reference": "pr_abc123",
    "3DSecure": "yes",
    "3DSecureUrl": "https://api.settleflow.io/redirect/3ds/pa_9f2c1d..."
  }
}

When 3DSecure=yes, the 3DSecureUrl field is present and Status is pending.

3. Redirect the customer

Return an HTTP 302 from your server, or open 3DSecureUrl in the customer's browser. They land on a brief hand-off page and are then taken to the issuer's challenge page.

res.redirect(302, result["3DSecureUrl"]);

4. Handle the return to ReturnUrl

After the challenge, the customer is sent back to the ReturnUrl you supplied. By default the gateway does this with an HTTP POST — an auto-submitted form carrying the outcome as body fields:

The body carries the standard response — the same fields you get from /v1/status and from a webhook:

FieldExampleMeaning
OperationTypepaymentAlways payment on this return.
Statuscapturedcaptured, authorized, pending, failed or cancelled.
Tidorder-42Your own order reference, as sent in step 2.
Referencepr_abc123Our payment reference.
Date2026-06-06 10:00:00Transaction date.
Amount10.00Amount in major units.
UserIdcustomer-7The Uid you sent in step 2.
MessagePayment was successfulHuman-readable outcome.
3DSecureyesWhether the payment was authenticated.
ErrorExpired cardAcquirer reason — present only when Status is failed.

So your ReturnUrl endpoint must accept POST, and any query string you put on the URL yourself is preserved untouched.

The return method is configured per application. If your integration expects a GET redirect instead — the same fields arriving as query parameters — ask us to switch your application to GET. Do not implement both and guess.

Whatever the method, treat this return as a hint, not as the source of truth: the customer may never come back. Steps 5 and 6 are what actually settle the order.

5. Confirm the final status

Query POST /v1/status to resolve the payment:

curl -X POST https://api.sandbox.settleflow.io/v1/status \
  -H "epro-api-key: pk_test_..." \
  -d '{ "Reference": "pr_abc123" }'

Possible outcomes:

StatusWhat to show the customer
capturedSuccess — order confirmed.
authorizedSuccess — capture pending.
pendingStill processing. Retry the status call after 1–2 s, or display a processing screen.
failedChallenge failed or declined — surface a recoverable error.
cancelledCustomer abandoned the challenge.

See the polling pattern on the status page for a defensive retry loop.

6. Trust the webhook for authoritative state

Even if the customer never returns to your ReturnUrl (closed tab, mobile browser lost focus…), the gateway will still deliver a webhook to your configured endpoint. Use that as the source of truth for order fulfillment — see Webhooks.

Testing

Use the dedicated 3DS test card in sandbox:

Card numberBehaviour
4000000000003220Forces a 3DS challenge and returns a 3DSecureUrl in sandbox.

See Sandbox & test cards for the full list.

On this page