# Finix.ThreeDSecure

Raises a 3D Secure (3DS) challenge inside your checkout page and reports the outcome to your front end.

Finix.js wraps the 3DS vendor SDK end to end. You don't install a vendor package, you don't allowlist a vendor script in your Content Security Policy, and the buyer never sees a vendor URL. The challenge renders in a full-screen transparent iframe that Finix.js creates and removes, so the buyer stays on your checkout page the whole time.

For the protocol, session states, and liability shift, see [3D Secure](/guides/online-payments/fraud-and-risk/3d-secure). For a walkthrough of a complete checkout, see [3D Secure using Finix.js](/guides/online-payments/fraud-and-risk/3d-secure/finix-js).

## Creating the session

Every challenge runs against a 3D Secure Session, and your server creates it by calling `POST /3d_secure_sessions`. Finix.js consumes the session you hand it. It never creates one.

Keep credentials and trusted values on your server
Create the session through an authenticated backend endpoint; never expose Finix API credentials in browser code. Derive and validate trusted values such as the merchant and amount on the server, and rate-limit the endpoint to prevent abuse.

## Choosing an entry point

| Entry point | Use this when |
|  --- | --- |
| `Finix.ThreeDSecure()` | The card is already on file, or you're PCI compliant and collect card details yourself. You decide when the challenge runs. |
| The `ThreeDSecure` form option | You already tokenize with [Finix.PaymentForm()](/js/payment-form). Finix.js calls your session endpoint, raises the challenge, retries, and manages the submit button for you. |


The form option is the shorter integration of the two. Reach for the class when you aren't using the payment form.

## Statuses and callbacks

Both entry points report the same three statuses.

| Status | Meaning |
|  --- | --- |
| `initialized` | Finix.js has initialized the challenge component |
| `succeeded` | Authentication finished. Submit the payment and reference the session ID |
| `failed` | Authentication did not finish. Read `error.code` for the cause |


Every 3DS callback receives one object.

| Property | Type | Description |
|  --- | --- | --- |
| `session` | `object | null` | The 3D Secure Session. `null` only on the payment form path, when `createSession` fails or returns an unusable session |
| `status` | `string` | `initialized`, `succeeded`, or `failed` |
| `error` | `object` | Present only when `status` is `failed` |


The `error` object has two properties:

- `code` - A Finix.js error code. See [Error codes](#error-codes).
- `reason` - A human-readable description of the failure, suitable for logging.


These codes are produced by Finix.js and the 3DS vendor. They are a separate enum from the `failure_code` field on the 3D Secure Session itself, so don't map one onto the other.

## The ThreeDSecure class


```text
Finix.ThreeDSecure(environment)
```

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `environment` | `string` | Yes | Finix environment (`sandbox` or `prod`) |


Returns a `ThreeDSecure` instance. Both of its methods return that same instance, so calls chain.

Selects the environment to authenticate against, and the vendor credentials Finix.js uses internally.

| Value | Environment |
|  --- | --- |
| `"sandbox"` | Sandbox |
| `"prod"` | Production |


These are the same environment strings the rest of Finix.js accepts. Use `sandbox` with `https://finix.sandbox-payments-api.com` and `prod` with `https://finix.payments-api.com`.

Raises the challenge for a session.


```text
threeDS.validate(threeDSecureSession, callback)
```

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `threeDSecureSession` | `object` | Yes | The entire response body from your server's `POST /3d_secure_sessions` call |
| `callback` | `function({ session, status, error })` | No | Inline form of `on()`. Runs on every status change |


Pass the response object through whole rather than picking fields out of it. Returns the `ThreeDSecure` instance.

Registers a listener for one status.


```text
threeDS.on(status, callback)
```

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `status` | `string` | Yes | The status to listen for: `initialized`, `succeeded`, or `failed` |
| `callback` | `function({ session, status, error })` | Yes | Runs when the challenge reaches that status |


Call `on()` once for each status you want to handle. Returns the `ThreeDSecure` instance.

The class supports three equivalent styles. Use whichever fits your checkout code.

- **Separate calls** - Hold the instance in a variable, then call `validate()` and `on()` against it.
- **Chained** - Chain `on()` onto the return value of `validate()`.
- **Inline callback** - Pass a single callback to `validate()` and branch on `status`.


## The ThreeDSecure form option

An optional object in the [options](/js/options) you pass to `Finix.PaymentForm()`. Providing it turns on 3D Secure for the form: Finix.js tokenizes the card, calls your session endpoint, raises the challenge, and reports both results to `onSubmit`.

| Property | Type | Required | Description |
|  --- | --- | --- | --- |
| `createSession` | `async function(token)` | Yes | Calls your backend and resolves with a 3D Secure Session |
| `onStatusChange` | `function({ session, status, error })` | No | Runs on every status change |


A hook to your own session endpoint. Finix.js calls it once the card is tokenized, and expects it to resolve with the response body from `POST /3d_secure_sessions`.


```text
createSession: async (token) => ThreeDSecureSession
```

- `token` - The token Finix.js created for the card details in the form.


Handing the call to Finix.js is what makes this path shorter than the class. Because Finix.js owns the timing, it calls your endpoint at the right moment, asks for a fresh session when a buyer cancels or a challenge fails, and disables and re-enables the submit button around the challenge. None of that is automatic when you drive the class yourself.

If `createSession` throws, rejects, or times out, the form reports `SESSION_API_FAILED`. If it resolves with something unusable, the form reports `SESSION_API_MALFORMED`. In both cases `session` is `null`.

Optional. Finix.js raises the challenge, retries, and manages the button without it, so use `onStatusChange` for your own analytics or buyer-facing messaging.


```text
onStatusChange: ({ session, status, error }) => void
```

It receives the same payload as the `on()` callbacks. The same callback is also documented on the [Callbacks](/js/callbacks) page alongside `onLoad`, `onUpdate`, and `onSubmit`.

The option works with the submit button the form renders and with your own button calling `form.submit()`. Finix.js manages the enabled state of the form's own button. With a custom button, keep using `onUpdate` and `hasErrors` as you do today. See [Form Instance](/js/form-instance).

## Submit response

When the `ThreeDSecure` option is set, the response passed to `onSubmit` carries a `threeDSecure` key alongside `data`.

| Key | Description |
|  --- | --- |
| `data` | The token. See [Token Response](/js/token-response) |
| `threeDSecure` | The authentication result: `{ session, status, error }` |


Read status, not session.state
`threeDSecure.session.state` still reads `PENDING` when `status` is `succeeded`. The API state only advances once Finix processes the authentication result from the vendor, which happens after your `onSubmit` runs. Decide whether to charge from `threeDSecure.status`, then submit the payment referencing `threeDSecure.session.id`.

## Error codes

| Code | Meaning |
|  --- | --- |
| `CARD_NOT_ENROLLED` | The session came back `FAILED`. The card can't be authenticated. This isn't a decline, so whether you charge it anyway is a policy decision |
| `CHALLENGE_FAILED` | The issuer declined the authentication. No further detail is available |
| `USER_CANCELLED` | The buyer closed the overlay |
| `MERCHANT_CANCELLED` | The challenge was cancelled programmatically |
| `CHALLENGE_TIMEOUT` | The challenge page didn't load within 20 seconds. Retryable |
| `CHALLENGE_LOAD_FAILED` | The challenge component couldn't be initialized. Retryable |
| `SESSION_API_FAILED` | `createSession` threw or timed out. Payment form only, and `session` is `null` |
| `SESSION_API_MALFORMED` | `createSession` returned an unusable session. Payment form only, and `session` is `null` |
| `ERROR` | An unclassified vendor failure. The vendor's own message is logged to the browser console |


Retrying `CHALLENGE_TIMEOUT` or `CHALLENGE_LOAD_FAILED` means creating a new session first. Sessions expire five minutes after they're created, and each one covers a single payment. On the payment form path, Finix.js calls `createSession` again for you.

## 3D Secure Session object

The `session` property in every callback is the object your backend returned from `POST /3d_secure_sessions`, unchanged.


```json
{
  "id": "3d_secure_session_f3fG3Zj8K958MTP5vpXAXX",
  "created_at": "2026-08-13T16:37:59.211335Z",
  "updated_at": "2026-08-13T16:37:59.469140Z",
  "3d_secure_authentication": null,
  "3d_secure_authentication_session_id": "tds_visa_0367b8b7ebd8",
  "3d_secure_authentication_url": null,
  "3d_secure_version": null,
  "amount": 1000,
  "currency": "USD",
  "authentication_type": null,
  "failure_code": null,
  "failure_message": null,
  "merchant_id": "MUmUNeW5N9Yw8RkLhLcGghy4",
  "payment_instrument_id": null,
  "redirect_url": null,
  "state": "PENDING",
  "tags": {}
}
```

Finix.js uses camelCase for its own options and callbacks, but this object comes straight from the Finix API and keeps its snake_case field names. That mix is expected.