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)
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 ✅
GET /instance/qr correctly returns passkeyStage/passkeyOpenUrl instead of QR during the ceremony ✅
- QR rotation pauses during the ceremony ✅
- 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.
Environment
evoapicloud/evolution-go:0.7.2), deployed on VPS behind HTTPS proxyPASSKEY_PUBLIC_URLset to the public API base (reachable by the browser; ceremony endpoints respond correctly)passkey-helperloaded unpacked in Chrome (macOS), same Google account as the phone's Google Password ManagerWhat works (up to the last step)
POST /instance/connect+ QR flow: on scan, the server demands passkey;PairPasskeyRequestis emitted, ceremony token created,#wapkURL logged — all as documented indocs/wiki/guias-api/passkey-pairing.md✅GET /instance/qrcorrectly returnspasskeyStage/passkeyOpenUrlinstead of QR during the ceremony ✅navigator.credentials.get()succeeds, assertion is POSTed to/passkey-ceremony/{token}/response→{"ok":true}, stage moves toawaiting_confirmation✅The bug
Stage never leaves
awaiting_confirmation.PairPasskeyConfirmationnever arrives; noPairPasskeyErroreither. Ceremony expires (TTL) and the cycle restarts.Four attempts, distinct conditions, same result for the Business account:
Received stream end frame→ EOF); no confirmationwhatsapp.com)awaiting_confirmationforever, server silentKey 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):
Earlier attempt (1) showed a different failure mode — socket closed by the server right after the response:
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
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)SendPasskeyResponsethat isn't wired yet?Happy to run patched builds / provide more logs (WADEBUG etc.) — we can reproduce consistently with this account.