Skip to content

[EPIC] DodoPayments 3DS 'Requires customer action' stuck-payments incident #4440

Description

@koala73

Incident

Dozens of DodoPayments subscription payments are stuck in status "Requires customer action" (3DS/SCA not completed). The same customers stack 4-5 duplicate attempts, and the overlay freezes on "Processing… / Payment Link Expires in 00:00." This is multi-factor: a mix of Dodo-overlay reality our integration got wrong and our own return/webhook handling being blind to the state.

How a 3DS payment dies (mechanism)

  1. Card needs 3DS. Dodo's overlay runs the challenge via Hyperswitch inside the iframe; banks that block iframe rendering force a full-page redirect to the bank (the overlay iframe sandbox has no allow-top-navigation, so redirects bubble out via postMessage).
  2. After the bank, Dodo returns the user to return_url = bare origin → the welcome marketing page (regression feat: serve welcome at root and dashboard at /dashboard #4307), which never reconciles the return → the user gets no success/failure signal.
  3. So the user retries → a brand-new payment (no idempotency key; dedup guard can't see pre-webhook attempts) → the stacked duplicates on the dashboard.
  4. Our webhook drops requires_customer_action/processing (records them as status:"processed" but persists no pending state) → we're blind: no pending-payment signal, so no duplicate-prevention, no reconciliation, no resume path → they rot at "requires_customer_action" forever. (Dedup is by webhookId, so later distinct transitions still arrive — the harm is the lost pending signal, not blocked transitions.)

What is NOT the cause (don't chase)

The backend checkout payload is correct: on-session (no confirm/payment_method_id, so Dodo does present the 3DS UI), correct product_cart/metadata, and there is no expiry param to set on checkoutSessions.create. The "00:00" timer is Dodo's showTimer on the session's server-side expires_at (24h default, since we don't set confirm=true) — a symptom, not the root cause.

Child issues

P0 — stops the bleeding:

P1 — correctness + duplicates:

P2 — follow-ups (flagged, not yet split out):

How this was diagnosed

Four adversarial agents (backend checkout-creation params, frontend overlay/3DS, webhook status handling, Dodo docs research) + direct reads of SDK 1.8.0 (dodopayments-checkout/dist/index.esm.js) and an independent Codex review converged on the same multi-factor picture above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcriticalCritical issue requiring immediate attentionepicUmbrella tracking issue for multiple related child issues

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions