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 samechallengeId, which drives the identical decision flow described below.
- Webhook (push)
- Polling the API (pull)
Subscribe to the Use
card.3ds.challenge.created event. It fires as soon as a challenge is created for a card no polling required.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.amount, currency, decimals, and the merchant display name are always present. amount is a non-negative minor-unit integer encoded as a decimal string; "0" is valid. The merchant ID remains optional and is never used as a fallback for the display name or signed decision.
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
decision (approve or decline). The response contains the complete structured EIP-712 typed data:Gnosis Pay), version (1), expected chain ID, primary type, and exact field names, types, and order shown above. Then compare message.decision, challengeId, amount, currency, decimals, and merchantName exactly with the selected decision and the challenge response.Do not format message.amount as a major-unit decimal. It is the unchanged minor-unit integer string from the challenge ("2599", or "0" for a zero amount), which avoids locale and rounding ambiguity. Use decimals only to format the amount in your user interface. Do not request a wallet signature if any value is unexpected.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
5
Poll for the outcome
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.Testing in sandbox
Use the Card Transaction Simulator to exercise the full integrator-facing flow without creating a transaction through the live card network. The simulator replaces Apata for the test challenge, but it deliberately keeps the real partner webhook, polling, wallet-signature, and decision APIs in the path.1
Start a 3DS authorization
Send an authorization to the Simulator API with The request returns
3ds set to true:202 Accepted with a simulation_id. The challenge expires after five minutes.2
Discover and decide the challenge
Receive
card.3ds.challenge.created through your configured partner webhook or discover the challenge through GET /user-api/3ds/challenges. Then use the same signed approve or decline flow described above; there is no simulator-only decision endpoint.3
Check the simulation result
Poll the simulation separately from the 3DS challenge:Approval continues to the Pismo simulator and returns its result under
transaction. Decline and timeout finish without calling Pismo.You do not need funds to test challenge delivery, polling, signing, decline, or timeout. You need sufficient available balance only if you want the Pismo authorization after an approved 3DS decision to succeed. A successful 3DS approval can therefore produce a completed simulation whose nested transaction is denied for insufficient balance.