API reference

3-D Secure

3-D Secure (3DS) is the step where the cardholder authenticates with the issuing bank, usually with an SMS code or a banking app. The platform runs it for you; your integration redirects the customer and reacts to the result.

When 3DS happens

Control it per payment with payment_method_options.card.three_d_secure:

Value Behavior
automatic (default) The platform decides. Payments within your non-secure limits that pass fraud screening may skip 3DS; everything else uses it.
required Always authenticate.
none Never authenticate. Only possible when your account is allowed non-secure payments and the amount is within your limits; otherwise the call fails with 400 non_secure_not_allowed.

The payment reports what was requested and what applied in three_d_secure.requested and three_d_secure.effective.

Register the return URL first

After authentication the customer is sent back to your return_url. That URL must be on a website you registered or match a callback URL you registered under Settings → Websites & callbacks in the merchant panel (see the authentication guide). Otherwise the payment is refused with 400 return_url_not_allowed and param: return_url. Always send return_url when 3DS may be needed, on POST /v1/payment_intents or on POST /v1/payment_intents/{id}/confirm.

The flow

  1. Confirm the payment (create with confirm: true, or call /confirm).
  2. If authentication is needed the response has status: requires_action and a next_action.
  3. Send the customer where next_action says.
  4. The customer authenticates at the bank. The bank reports back to the platform, which completes the payment.
  5. The platform redirects the customer to your return_url with payment_intent=pi_… appended to the query string.
  6. On your return page, read the payment on your server and show the outcome. Do not trust the redirect itself.

Step 2 and 3: next_action

next_action.type is one of:

{
  "id": "pi_01J9ZQ4V8K2M3N5P7R9T1W3Y5B",
  "object": "payment_intent",
  "amount": 12500,
  "currency": "try",
  "status": "requires_action",
  "capture_method": "automatic",
  "three_d_secure": {"requested": "automatic", "effective": "required"},
  "next_action": {
    "type": "redirect_to_url",
    "redirect_to_url": {
      "url": "https://pay.example.com/3ds/start/att_01J9ZQ4V8K2M3N5P7R9T1W3Y5C",
      "return_url": "https://shop.example.com/pay/return"
    }
  },
  "return_url": "https://shop.example.com/pay/return",
  "created": 1790000000,
  "livemode": false
}

Step 6: your return page

curl https://pay.example.com/v1/payment_intents/pi_01J9ZQ4V8K2M3N5P7R9T1W3Y5B \
  -H "Authorization: Bearer sk_sandbox_YOUR_KEY"
status Meaning
succeeded Paid (automatic capture). Fulfil the order.
requires_capture Authorized, waiting for your POST /v1/payment_intents/{id}/capture (capture_method: manual).
processing The bank has not answered yet. Wait for the webhook; do not charge again.
requires_payment_method Authentication or the payment failed. last_payment_error has the reason (for example authentication_failed); let the customer try another card.
requires_action The customer is still at the bank.

When the customer never comes back

Customers close tabs. Do not depend on the return redirect: subscribe to the payment_intent.succeeded, payment_intent.payment_failed and payment_intent.requires_action webhooks (see the webhooks guide) and reconcile on those. If you hold an order open, check it with GET /v1/payment_intents/{id} before releasing the stock.

Testing

In the sandbox, test cards cover the successful challenge, a failed authentication, a card that requires 3DS and an abandoned challenge. See the sandbox guide.