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.
Steps
1
Receive the challenge
Listen for these webhook events:
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
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
- 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_atagainst 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}instead of trusting what’s on screen. - Check ownership. Before reading a challenge or submitting a decision, check that the
cardholder_idbelongs to the user making the request.