Skip to main content

Overview

In-app 3DS push provisioning means the cardholder approves or declines a card transaction’s 3D Secure (3DS) challenge directly inside the app, in real time instead of being redirected to a bank page, sent an SMS one-time passcode, or otherwise taken out of the app for verification. While a transaction is in flight, the pending challenge is delivered to the app (by webhook or by polling the API), the cardholder reviews the merchant, amount, and expiry on screen, and approves or declines by signing with their connected wallet. There’s no redirect, no context switch, and no separate 3DS page.

End-to-end flow

Discovering a pending challenge

A challenge can be discovered one of two ways. Both produce the same challengeId, which drives the identical decision flow described below.
Subscribe to the card.3ds.challenge.created event. It fires as soon as a challenge is created for a card no polling required.
Use data.id directly as challengeId. data.notificationAttempt is a redelivery counter webhooks can be delivered more than once, so handle deliveries idempotently, keyed on data.id.
The webhook and the list/detail endpoints don’t use identical field names for the same data: the webhook’s merchant.name / merchant.mcc correspond to the REST endpoints’ merchant.displayName / merchant.categoryCode. Key off data.id (or id) and re-fetch via GET /3ds/challenges/{challengeId} if you need the canonical field names, don’t assume the two shapes match 1:1.

Deciding a challenge

Approve and decline go through the identical signed flow, there is no unsigned decline shortcut.
1

Show the verification prompt

Display the merchant, the amount (convert the minor-unit amount using the challenge’s own decimals value, never assume 2 decimal places), currency, and optional country / MCC. Count down to expiresAt, adjusted for clock skew using serverTime. If the countdown reaches zero, treat the challenge as expired and move to the next pending one, if any.
2

Request decision-bound typed data

Returns EIP-712 typed data bound to the decision (approve or decline) being made the typed data itself encodes which outcome is being signed, so a signature can’t be replayed against the other one.
3

Sign with the connected wallet

Pass the typed data to the wallet’s signTypedData method and have the cardholder confirm in their wallet.
4

Submit the signed decision

Send the resulting signature and nonce as headers, no key material or challenge secrets are ever transmitted, only the signature and nonce:
5

Poll for the outcome

While the status is pending or processing, keep polling. Stop as soon as a terminal status is returned and show it to the cardholder. If the poll itself errors, retry rather than declaring failure the decision may already be recorded server-side.

Dismissal and multiple challenges

A cardholder can dismiss a prompt without deciding it. Dismissal is local to the current foreground session only, it doesn’t cancel the challenge server-side, and the same challenge reappears on a fresh session. If more than one challenge is pending, present one at a time; after a challenge is dismissed, decided, or expires, immediately show the next pending one.

Endpoints