Overview
3D Secure (3DS) adds an extra layer of authentication for card payments. When a card issuer requires 3DS, the Flowlix API returns a requires_action status with a redirect URL. Your integration must handle this status and redirect the customer to complete authentication.
Flowlix supports 3DS 2.0 (the current standard mandated by PSD2/SCA in Europe). The decision on whether 3DS is needed is made by the card issuer — you don’t need to request it explicitly.
How it works
- Create a payment with
return_url
- The response returns
status: "pending" — the payment is being processed asynchronously
- Poll
GET /v1/payments/{id} every 2–3 seconds
- If 3DS is required, the status changes to
requires_action with a next_action.redirect_url (typically within 2–5 seconds)
- Redirect the customer to
next_action.redirect_url
- Customer completes authentication (SMS code, biometric, etc.)
- Customer is redirected back to your
return_url
- Continue polling — the status will become
succeeded or failed
The requires_action status may not appear in the initial create response. The upstream processor
needs a few seconds to determine whether 3DS is required. Always poll for status changes rather
than relying on the create response alone.
Creating a payment with 3DS support
Include return_url in your payment request. All card and customer sub-fields are required by the API — see Direct API for the complete list.
curl -X POST https://api.flowlix.dev/v1/payments \
-H "Authorization: Bearer fl_test_sk_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"amount": 5000,
"currency": "eur",
"return_url": "https://your-site.com/payment/complete",
"card": {
"number": "4635440000002223",
"exp_month": 2,
"exp_year": 2027,
"cvc": "196",
"holder_name": "Jenny Rosen"
},
"customer": {
"email": "jenny.rosen@example.com",
"first_name": "Jenny",
"last_name": "Rosen",
"country": "DE",
"phone": "+491701234567",
"address": "Kurfuerstendamm 21",
"city": "Berlin",
"zip": "10719"
}
}'
Handling the response
Initial response
The create response is always pending — the processor needs a few seconds to evaluate
whether 3DS is required:
{
"id": "pay_01h9z8xm2c7q4r8s9t0v1w2x3y",
"object": "payment",
"status": "pending",
"amount": 5000,
"currency": "eur",
"next_action": null
}
Polling for status
Poll GET /v1/payments/{id} every 2–3 seconds. Two outcomes:
No 3DS required — status moves directly to succeeded or failed.
3DS required — status changes to requires_action:
{
"id": "pay_01h9z8xm2c7q4r8s9t0v1w2x3y",
"status": "requires_action",
"next_action": {
"type": "redirect",
"redirect_url": "https://acs.bank.com/3ds/..."
}
}
Redirecting the customer
// Poll until status changes from pending
const poll = setInterval(async () => {
const res = await fetch(`/v1/payments/${paymentId}`, { headers })
const payment = await res.json()
if (payment.status === 'requires_action') {
clearInterval(poll)
window.location.href = payment.next_action.redirect_url
} else if (payment.status !== 'pending') {
clearInterval(poll)
showResult(payment.status) // succeeded or failed
}
}, 3000)
After 3DS
The customer returns to your return_url. Resume polling — the status will become
succeeded or failed:
curl https://api.flowlix.dev/v1/payments/pay_01h9z8xm2c7q4r8s9t0v1w2x3y \
-H "Authorization: Bearer fl_test_sk_..."
Timeout
If the customer doesn’t complete 3DS in time, the payment status changes to expired. Create a new payment to retry.
3DS Decline Codes
When 3DS authentication fails, the payment status becomes failed with a specific decline_code:
| Code | Description |
|---|
three_d_secure_failed | Customer failed authentication (wrong code, rejected) |
three_d_secure_timeout | Customer didn’t complete in time |
three_d_secure_not_supported | Card doesn’t support 3DS |
three_d_secure_error | 3DS infrastructure error (temporary) |
See 3D Secure Declines for detailed guidance on handling each code.
Test cards
| Card | Behavior |
|---|
4635 4400 0000 2223 | Triggers 3DS challenge (sandbox) |
4111 1111 1111 1111 | Approves without 3DS (sandbox) |