Skip to content

Passkey ceremony stuck at awaiting_confirmation — server never sends PairPasskeyConfirmation (0.7.2) #107

Description

@Gutoalvess

Environment

  • Evolution Go 0.7.2 (evoapicloud/evolution-go:0.7.2), deployed on VPS behind HTTPS proxy
  • PASSKEY_PUBLIC_URL set to the public API base (reachable by the browser; ceremony endpoints respond correctly)
  • Extension passkey-helper loaded unpacked in Chrome (macOS), same Google account as the phone's Google Password Manager
  • Account: WhatsApp Business (Android, dual-app device with regular WhatsApp on the same phone), locked behind mandatory passkey since ~2026-06-30 (the same account could not pair via Baileys — see evolution-api issue #2618 pattern)

What works (up to the last step)

  1. POST /instance/connect + QR flow: on scan, the server demands passkey; PairPasskeyRequest is emitted, ceremony token created, #wapk URL logged — all as documented in docs/wiki/guias-api/passkey-pairing.md
  2. GET /instance/qr correctly returns passkeyStage/passkeyOpenUrl instead of QR during the ceremony ✅
  3. QR rotation pauses during the ceremony ✅
  4. Extension panel appears on web.whatsapp.com, fetches the challenge, navigator.credentials.get() succeeds, assertion is POSTed to /passkey-ceremony/{token}/response{"ok":true}, stage moves to awaiting_confirmation

The bug

Stage never leaves awaiting_confirmation. PairPasskeyConfirmation never arrives; no PairPasskeyError either. Ceremony expires (TTL) and the cycle restarts.

Four attempts, distinct conditions, same result for the Business account:

Attempt Assertion timing after challenge Passkey used Observed
1 > 60s (challenge likely stale) synced via Google Password Manager WhatsApp closed the socket seconds after the response (Received stream end frame → EOF); no confirmation
2 ~39s wrong account's passkey (regular WhatsApp on the same phone — both apps share rpId whatsapp.com) the server DID react: a confirmation surfaced on the regular account's side. Pairing obviously didn't complete for Business, but this proves the confirmation leg works end-to-end through Evolution Go + extension
3 ~23s freshly created passkey on the correct (Business) account, stored in Google Password Manager (account A) assertion accepted at transport level; socket alive 2+ min; server sent nothing; ceremony expired
4 fast Business passkey recreated again, stored in a different Google account (hybrid/cross-device signing from the phone) identical: awaiting_confirmation forever, server silent

Key evidence — the asymmetry: same server, same extension, same browser, same ceremony flow; an assertion from the regular account's key gets a server reaction, while any assertion from the Business account's key (two freshly created passkeys, two different credential stores, platform + hybrid transports) gets pure silence. This rules out the extension, the ceremony plumbing, and the user's credential manager — and points at the Business account's server-side state (this account went through the mandatory-passkey lockdown around 2026-06-30 and had passkeys recreated several times since), or at an account-specific gap in the Shortcake confirmation leg.

Log excerpt around attempt 3 (nothing after the response submission besides our polling):

17:54:51 ceremony created (stage=challenge)
17:55:14 POST /passkey-ceremony/{token}/response -> 200 (stage=awaiting_confirmation)
17:55:18..17:57:07 only GET polling; no PairPasskeyConfirmation, no PairPasskeyError, socket alive
(ceremony expires at TTL)

Earlier attempt (1) showed a different failure mode — socket closed by the server right after the response:

17:22:41 [Client WARN] Received stream end frame
17:22:41 [Client/Socket ERROR] Error reading from websocket: failed to get reader: failed to read frame header: EOF

Also noteworthy: during an active ceremony, if the socket drops, the instance restarts and logs "Passkey ceremony in progress — pausing QR rotation" — but the new socket has a fresh pairing context while the ceremony store still holds the old challenge; that ceremony can never complete. Maybe the ceremony should be invalidated on socket restart.

Questions / hypotheses

  • Is the confirmation leg (PairPasskeyConfirmation) confirmed working against a real locked account by anyone yet? (guide says the feature is not yet validated end-to-end by the community)
  • Could the phone-side handoff screen (CRSC) be required and never triggered for dual-app (WhatsApp + WA Business on the same device) setups? The phone never shows any confirmation screen/code.
  • whatsmeow pinned commit: does it require any additional stanza after SendPasskeyResponse that isn't wired yet?

Happy to run patched builds / provide more logs (WADEBUG etc.) — we can reproduce consistently with this account.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions