3DS Gate

Quickstart

One server route, one redirect, one webhook. This walkthrough uses the sandbox key sk_test_demo, which needs no account.

Sandbox behaviour. Amounts ending in 01 authenticate, 02 fail, 03 return unavailable. Everything else is randomised deterministically by authenticationId.

1. Create the session

Server side only. The secret never reaches the browser.

POST /v2/authentications
Authorization: Bearer sk_test_demo

{
  "amount": 19901,
  "currency": "BRL",
  "messageVersion": "2.2.0",
  "card": { "brand": "mastercard", "bin": "555555", "last4": "4444" },
  "buyer": { "email": "[email protected]", "locale": "pt-BR" },
  "notificationUrl": "https://localhost:3000/api/3ds/notify",
  "returnUrl": "https://localhost:3000/checkout/return"
}

The response carries challengeUrl. Persist authenticationId on the order before redirecting.

2. Redirect the buyer

res.redirect(302, body.challengeUrl);

The buyer confirms with the issuer on the challenge host. Nothing runs on your page during this step.

3. Receive the notification

The service posts to notificationUrl. Verify before reading:

const expected = crypto
  .createHmac('sha256', ENDPOINT_SECRET)
  .update(rawBody)
  .digest('hex');

if (!crypto.timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex'))) {
  return res.sendStatus(401);
}

const { authenticationId, transStatus, eci } = JSON.parse(rawBody);

if (transStatus === 'Y') {
  // proceed to authorisation with authenticationId and eci
} else if (transStatus === 'N') {
  // decline
} else {
  // U or A: route per risk policy
}

4. Answer 200 quickly

Any 2xx marks the notification delivered. Retries run at 10 s, 1 min, 5 min, 30 min and 6 h, then the event is parked and shown in the dashboard. Handle duplicates — delivery is at-least-once, keyed by authenticationId.

5. Check the audit trail

GET /v2/authentications/ath_01J9Z4K2V6/events

Returns the ordered timeline: created, method step, challenge rendered, mode used, attempts, result. This is the same record used in a chargeback reply.

Go live

  1. Replace the key with sk_live_… from the dashboard.
  2. Register the production notification URL — sandbox URLs are not accepted in live mode.
  3. Whitelist the challenge host in your CSP frame-ancestors if you embed instead of redirecting.
  4. Run the conformance checklist before opening traffic: version fallback, U handling, signature check, idempotency.

Next: sandbox controls · challenge lifecycle