Recovering challenge results
A challenge can finish without your backend receiving a completion notification. Your backend can call Get Challenge Result using the original session ID to retrieve an available result. Recovery is initiated and scheduled by your integration.
Backend responsibilities
- When authentication returns
challengeordecoupled-challenge, persist the session ID, challenge start time, recovery deadline, and attempt count. Use a durable scheduler that continues after the browser closes or your backend restarts. - Route completion callbacks, webhook triggers, and scheduled recovery through the same coordinator. Allow one outstanding result request per session across all backend instances. Prevent duplicate jobs from consuming separate retry budgets.
- Use a private application key with
3ds:session:authenticate. Keep it on your backend, and authorize access to the session for the customer making the request. - On
200, save the authentication response and cancel outstanding recovery work. Useauthentication_statusto decide the authentication outcome. A successful HTTP response can contain a failed authentication. - On an error, follow the endpoint's retry contract. Track retrieval failure separately from authentication status.
Do not start recovery for frictionless authentication. Its outcome and authorization data come from the authentication response.
Choose a recovery window
The following schedules are starting points for interactive challenges, not availability guarantees or universal issuer timeouts. Keep the deadlines and attempt limits configurable.
| What your backend knows | Suggested schedule |
|---|---|
| A completion notification confirmed the challenge ended | Query once, then use increasing delays for a pending result, for example 2, 5, 10, then 15–30 seconds. Stop within 60–120 seconds of confirmed completion. |
| No completion notification arrived | Start around minute 5–6 after challenge initiation, or begin sparse checks around minute 2–3. Keep opportunities available through approximately minute 11–12, including a check near the end of the window. |
| Decoupled authentication or a longer configured mobile challenge | Use a separate schedule based on that flow's configured duration. Do not apply the interactive 12-minute example as a universal deadline. |
For a simple no-callback schedule, plan checks near minutes 5, 7, 9, 10, 11, and 12. Add small jitter to spread traffic while keeping the final opportunity within your deadline. Avoid using a two-minute window starting at the first lookup: stopping at minute 7 can miss an issuer timeout result around minute 10.
If completion arrives while recovery is pending, update the existing job's schedule using the completion time. Do not create a second polling loop. Share the attempt budget across triggers and retain enough attempts for late availability. Check the deadline before every retry; a retryable error does not extend it indefinitely.
Limit total recovery concurrency and request rate across sessions as well as per session. Automatic retries are limited to 424 responses with error.service_status of 404 or 500, 503 responses with code = 3ds_result_transport_error, and transport failures where your backend receives no HTTP response. They all share the same deadline and attempt budget. The specific 503 code identifies a connection failure or timeout during result retrieval; it does not make every 503 retryable. Stop polling on other HTTP errors, including 408 and 429; investigate the cause before making further requests. No fixed polling schedule guarantees capacity at every transaction volume.
When the window ends
Record that result retrieval timed out in your application. This is not a new Basis Theory authentication status. Preserve any known successful or failed status received earlier; do not replace it because authorization data could not be retrieved.
A 424 with error.service_status = 404 cannot distinguish a result that has not arrived from one that was previously retrieved and removed or expired. Missing results also do not prove whether the issuer received the challenge request.
A result may become unavailable after retrieval. Reuse your saved response and the result stored by Basis Theory. A connection loss or process failure before persistence can leave the full result unavailable even after another attempt. Recovery cannot guarantee an outcome for every session.
Events and support
3ds.session.challenge-completed is a completion signal and can omit authentication status. 3ds.session.challenge-result-retrieved is emitted when a result is newly retrieved and saved through the API. Neither event guarantees that a missing result will be fetched automatically. Cached reads do not generate another retrieval event.
Do not wait for a webhook before accepting a successful result response. If a result is stored but an expected webhook is missing, investigate delivery separately.
Contact Basis Theory Support when a terminal error occurs or the recovery window ends without the required result. Include:
- Session ID and the relevant account or tenant.
- Challenge start, completion, and lookup times in UTC.
- Basis Theory HTTP statuses,
error.service_status, sanitized error codes, and available request IDs. - Whether any caller received
200, what high-level status was saved, and whether a webhook was received.
Do not include private API keys, raw challenge messages, card data, or authentication values in logs or support messages. A missing result is not a reason to automatically create a new authentication or retry a payment.