> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gnosispay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Handle 3DS challenges

> Let users approve or decline 3D Secure challenges for online card payments.

When a user pays online, the merchant can ask for 3D Secure (3DS) verification. Gnosis Pay sends your backend a challenge, you show it to the user in your app, and you submit their decision before the challenge expires.

> [!NOTE]
>
> 3DS is webhook-driven. Don't poll for a list of challenges. Wait for the webhook events in the steps that follow. See [Configure webhooks](https://docs.gnosispay.com/platform-integration/webhook/setup-webhooks).

## Steps

**1. Receive the challenge**

Listen for these webhook events:

| Event | Meaning |
| - | - |
| `card.3ds.challenge.created` | A new challenge needs the user's decision |
| `card.3ds.challenge.retry_requested` | The user needs to decide again on an existing challenge |

Notify the user right away, for example with a push notification, because the challenge expires.

**2. Show the challenge to the user**

Display these details from the event, so the user can recognize the payment:

* Amount and currency
* Merchant
* Expiry time

If you need the latest state, for example when the user opens the challenge some time after the notification, call `GET /3ds/challenges/{challenge_id}` with the `cardholder_id`.

**3. Submit the decision**

Send the `cardholder_id` and the user's decision, `approve` or `decline`, to `POST /3ds/challenges/{challenge_id}/decision`.

You must submit before the challenge's `expires_at`.

**4. Wait for the final state**

A `202` response means the decision is being processed, not that it's complete. Wait for the final state before you tell the user the payment was approved or declined.

## Build a safe decision screen

> [!WARNING]
>
> Never approve a challenge after its expiry time, as shown by the API's `expires_at`.

* **Prevent duplicate submissions.** Disable the approve and decline buttons as soon as the user taps one, and send at most one decision per challenge from your backend.
* **Reject stale challenges.** Before showing or submitting, check `expires_at` against the current time. If the challenge has expired, show it as expired and remove the buttons.
* **Show the latest state.** If the user returns to a challenge later, read it again with [`GET /3ds/challenges/{challenge_id}`](https://docs.gnosispay.com/api-reference/partner-api/3ds/get-a-3ds-challenge) instead of trusting what's on screen.
* **Check ownership.** Before reading a challenge or submitting a decision, check that the `cardholder_id` belongs to the user making the request.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.