# Adyen 3DS2 challenge loop: threeds2.challenge never returns a result

## TL;DR
A stuck 3DS2 challenge almost always means your frontend never posts the challenge outcome back to your backend, so /payments/details is never called and the flow hangs. Wire the challenge completion event to submit the challenge result to your server, then call /payments/details. Fix the missing callback and the loop disappears.

```text
adyen 3DS2 challenge loop: threeds2.challenge never returns a result
```

## Steps

1. Confirm where it stalls: open your browser dev tools and watch network traffic after the shopper completes the challenge. If no request goes to your backend, the completion handler is missing. Success check: you can see the challenge iframe close with no follow-up request.

2. In your Components/Drop-in config, handle the challenge completion by collecting the challenge result payload the SDK hands you and POSTing it to your backend. Success check: your backend receives a request containing the 3DS challenge result after the shopper finishes.

3. On your backend, pass that payload into a /payments/details call. Success check: /payments/details returns a real resultCode (Authorised, Refused, Cancelled, Error) instead of hanging.

4. Make sure your handler covers both outcomes: challenge completed AND challenge errored or timed out. A timeout with no error path looks exactly like a loop. Success check: forcing a challenge timeout routes the shopper to your error screen.

5. Re-run the full flow in test with the 3DS2 test cards from the Adyen docs. Success check: challenge completes, /payments/details returns Authorised, and the shopper lands on the success page.

## When this applies

- The native 3DS2 challenge screen appears but the payment never resolves; the shopper sits on a spinner or blank screen.
- threeds2.challenge fires and nothing follows; no /payments/details call in your logs.
- An agent-built integration "works without 3DS" but stalls as soon as a challenge is required.

## When it doesn't

- Shopper cancels the challenge (resultCode CANCELLED); that is abandonment, not a loop.
- The redirect-based 3DS flow returns with no resultCode; that is a return-URL problem.
- /payments itself returns 403; that is a merchant 3DS2 enablement issue.

## Tool + version compatibility

Adyen Checkout API v68+, Drop-in / Components v5+, EMV 3-D Secure 2 native challenge flow.

### "adyen 3ds challenge hangs"
Same root cause in nearly all cases: missing completion callback. Start at step 2.

### "adyen threeDS2Result not returned"
Your code never received or never forwarded the result. Check both the SDK event wiring and the backend endpoint.

### "adyen challenge iframe stuck"
If the iframe never closes, also check that your Content Security Policy allows the 3DS provider's frames; a blocked frame looks like a hang.

## Why it happens

3DS2 is a three-party dance: your frontend, the issuer's challenge page, and your backend. Adyen's SDK handles the iframe, but YOUR code owns the last step, forwarding the challenge outcome to /payments/details. Agent-generated integrations commonly mount the challenge action and assume Adyen finishes the job, so the flow dead-ends at the most security-critical moment.

## Edge cases / pitfalls

- Redirect-based 3DS (not native) uses redirectResult instead; mixing the two payloads breaks /payments/details.
- Some issuers' challenge pages are slow; add a visible timeout (60-90s) so a genuinely stuck challenge fails gracefully instead of spinning forever.
- Do not auto-retry a timed-out challenge with the same session; start a fresh payment.
- Use the documented 3DS2 test card set in test, not real cards.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_RC9vls-M2_sAV_K7UVEWmQ
