Skip to content

feat: add cancelPairingCode() and requestPairingCode() on QR clients#22449

Merged
BenyFilho merged 6 commits intowwebjs:mainfrom
Adi1231234:feature/cancel-pairing-code
Mar 24, 2026
Merged

feat: add cancelPairingCode() and requestPairingCode() on QR clients#22449
BenyFilho merged 6 commits intowwebjs:mainfrom
Adi1231234:feature/cancel-pairing-code

Conversation

@Adi1231234
Copy link
Contributor

@Adi1231234 Adi1231234 commented Feb 10, 2026

Summary

Add cancelPairingCode() method and allow requestPairingCode() to work on QR-initialized clients (not just clients initialized with pairWithPhoneNumber option).

Changes

requestPairingCode() - allow on QR-initialized clients

When called on a client that was initialized in QR mode, onCodeReceivedEvent may not be exposed yet (it is only set up in initialize() when pairWithPhoneNumber is configured). Added exposeFunctionIfAbsent call at the beginning of requestPairingCode() to ensure the callback is available regardless of initialization mode.

cancelPairingCode() - new method

Cancels an active pairing code session and returns to QR code mode:

  1. Clears the window.codeInterval refresh timer
  2. Calls PairingCodeLinkUtils.initializeQRLinking() to switch WhatsApp Web back to QR mode
  3. Re-emits the current QR code via onQRChangedEvent using the cached getQR helper

window.getQR - cached QR builder

The getQR function (which builds the full QR string from registration keys) is now saved on window during initialize(). This is necessary because:

  • Store.Cmd.refreshQR() is no longer available in current WhatsApp Web versions
  • getADVSecretKey() returns null after setPairingType("ALT_DEVICE_LINKING") is called
  • The change:ref listener registered in initialize() already uses this closure, so caching it on window allows cancelPairingCode() to reuse the same keys

TypeScript definition

Added cancelPairingCode(): Promise<void> to index.d.ts.

Use case

Allows switching between QR code and pairing code authentication without destroying and recreating the client. This enables UIs where users can toggle between QR scanning and phone number pairing on the same screen.

const client = new Client({ /* QR mode (default) */ });
client.initialize();

// User wants to switch to pairing code
client.on("qr", () => { /* show QR */ });
const code = await client.requestPairingCode("1234567890");
// show pairing code to user

// User wants to switch back to QR
await client.cancelPairingCode();
// qr event fires again with fresh QR

Testing

Tested full round-trip flow via E2E tests:

  1. QR displayed -> switch to pairing code (received valid code)
  2. Cancel pairing -> QR returns with valid scannable code
  3. Switch to pairing again -> new code received
  4. Cancel again -> QR returns

All transitions work without destroying the client.

Closes #22390

@Adi1231234 Adi1231234 force-pushed the feature/cancel-pairing-code branch from 6f49eb4 to c866e50 Compare February 10, 2026 08:00
@Adi1231234 Adi1231234 force-pushed the feature/cancel-pairing-code branch 5 times, most recently from 312c35a to 29fead3 Compare February 10, 2026 10:02
@Adi1231234 Adi1231234 changed the title feat: add cancelPairingCode() and allow switching between QR and pairing code feat: add cancelPairingCode() and allow requestPairingCode() on QR-initialized clients Feb 10, 2026
@BenyFilho BenyFilho added the waiting for testers Needs testing label Feb 11, 2026
@BenyFilho BenyFilho mentioned this pull request Feb 18, 2026
2 tasks
@Adi1231234
Copy link
Contributor Author

Hey @purpshell @BenyFilho, just checking in on this PR 🙂

It adds cancelPairingCode() and lets you call requestPairingCode() on clients that were initialized with QR. Pretty useful for apps that want to let users switch between QR and phone number.

Any chance you could take a look? Happy to make changes or jump on Discord if needed!

@BenyFilho
Copy link
Member

Hey @purpshell @BenyFilho, just checking in on this PR 🙂

It adds cancelPairingCode() and lets you call requestPairingCode() on clients that were initialized with QR. Pretty useful for apps that want to let users switch between QR and phone number.

Any chance you could take a look? Happy to make changes or jump on Discord if needed!

Need user tests to validate it, and update your changes with branch main

@Adi1231234 Adi1231234 force-pushed the feature/cancel-pairing-code branch from 29fead3 to d4b22ac Compare March 9, 2026 23:03
@Adi1231234
Copy link
Contributor Author

Hey @BenyFilho, just rebased onto the latest main so it's up to date and CI is green.

I keep running into conflicts because other PRs get merged while mine stay open, and I have to keep rebasing them. I'd really love to get these merged so I can stop chasing conflicts 😅

You mentioned user tests, could you clarify what exactly you're looking for? I'd be happy to add whatever is needed to move this forward.

@BenyFilho
Copy link
Member

Need user tests to validate it, and update your changes with branch main

Need user tests to validate it, and update your changes with branch main

@Adi1231234 Adi1231234 force-pushed the feature/cancel-pairing-code branch from d4b22ac to 1a2694e Compare March 10, 2026 01:40
@github-actions github-actions bot added api changes API modifications typings Type definitions labels Mar 10, 2026
- requestPairingCode() now exposes onCodeReceivedEvent if needed,
  so it works on clients initialized in QR mode
- Add cancelPairingCode() method to stop pairing and return to QR
- Update TypeScript definitions
@Adi1231234 Adi1231234 force-pushed the feature/cancel-pairing-code branch from 1a2694e to d2784c1 Compare March 10, 2026 01:48
@Adi1231234 Adi1231234 changed the title feat: add cancelPairingCode() and allow requestPairingCode() on QR-initialized clients feat: add cancelPairingCode() and requestPairingCode() on QR clients Mar 10, 2026
@Adi1231234
Copy link
Contributor Author

Hey @BenyFilho, thanks for looking at this!

Rebased onto latest main and CI is green.

This feature lets users switch between QR and pairing code without destroying the client, which is pretty useful for apps with a linking UI. I've been using it in my own app and tested the full round trip (QR -> pairing code -> cancel -> QR again) multiple times.

If anyone here needs this functionality or wants to give it a spin, I'd love your feedback. I really need my open PRs merged so if anyone can help review or test them, I'm happy to pay for your time. Just ping me!

@BenyFilho
Copy link
Member

Hey @BenyFilho, thanks for looking at this!

Rebased onto latest main and CI is green.

This feature lets users switch between QR and pairing code without destroying the client, which is pretty useful for apps with a linking UI. I've been using it in my own app and tested the full round trip (QR -> pairing code -> cancel -> QR again) multiple times.

If anyone here needs this functionality or wants to give it a spin, I'd love your feedback. I really need my open PRs merged so if anyone can help review or test them, I'm happy to pay for your time. Just ping me!

Hi,

This is a community project, so changes need to be reviewed and validated by other users. They should be useful for the community in general and must not break existing functionality.

I understand that these changes are important for your use case, but they also need to make sense for the community as a whole. The approval decision is based on technical and community impact, not on financial aspects.

Thanks for your understanding.

@Adi1231234
Copy link
Contributor Author

Hey @BenyFilho, just want to clarify - I absolutely did not mean paying for approvals. I meant compensating someone for their time reviewing and discussing the best approach, even if the conclusion is that my implementation needs rework.

I share the same philosophy I've seen you promote - if you have a problem, fix it yourself. That's why I've put in the work and opened multiple PRs. Unfortunately it's been hard to get people to test and review them.

I truly believe in this project and would rather contribute back than maintain my own fork with all the fixes. So yes, I'm genuinely willing to pay for help and support to move these fixes forward - for the benefit of the whole community.

Thanks! 🙏

@sofi-ans
Copy link

sofi-ans commented Mar 16, 2026

@Adi1231234 Cool idea, switching between QR and pairing code without recreating the client would be really useful.

I spent some time looking at the WA Web internals to understand the flow, and I think cancelPairingCode() has a problem that would make the QR it emits unscannable.

The issue is with how window.getQR works. It's a closure created during initialize() that captures the ADV secret key once. But by the time cancelPairingCode() runs, that key is no longer valid - initializeAltDeviceLinking() (called during requestPairingCode) deletes it from IndexedDB, and then initializeQRLinking() (called during cancel) generates a completely new random one. So the closure still has the original key, but IndexedDB has a different one.

This actually causes a logout, not just a broken QR. When a phone scans the code, the server uses the key from the QR string to compute an HMAC, and the web client validates by reading the current key from IndexedDB. Since they don't match, logoutAfterValidationFail() kicks in. I looked at how WA Web's own QR component handles this - it reads the key fresh from IndexedDB on every render instead of caching it, which avoids this entirely.

A couple more things I noticed:

initializeQRLinking() is async (it writes to IndexedDB) but it's called without await, so the QR gets emitted before the state reset is done.

Also, WA Web's internal resetLinkDeviceState always calls require('WAWebLaunchSocketUtils').refreshQR() before switching modes - this restarts the WebSocket and gets fresh refs from the server. Without it Conn.ref is probably expired since refs only live for ~60 seconds. You're right that Store.Cmd.refreshQR() is gone, but WAWebLaunchSocketUtils.refreshQR() is still there and that's what the internal code uses.

The exposeFunctionIfAbsent part in requestPairingCode looks good though, that makes sense.

@BenyFilho BenyFilho requested a review from aliyss March 18, 2026 13:07
@BenyFilho BenyFilho added the approved Confirmed by maintainers label Mar 18, 2026
@Adi1231234 Adi1231234 force-pushed the feature/cancel-pairing-code branch from 25c26f6 to 0c2b592 Compare March 19, 2026 20:48
@Adi1231234
Copy link
Contributor Author

@BenyFilho Thanks!

@sofi-ans Good catch, you were right. I tested it and the QR scan was actually failing. Fixed it - getQR now reads the key fresh instead of using the cached one, and cancelPairingCode follows the same flow WA Web uses internally (refreshQR + initializeQRLinking). Works now.

the getQR closure captured advSecretKey once during initialize(), but
requestpairingcode/cancelPairingCode changes the key in IndexedDB.
the stale closure key caused QR scans to fail with HMAC validation error.

fix: read key dynamically via WAWebUserPrefsMultiDevice.getADVSecretKey()
(synchronous in-memory cache) on every getQR call, matching how WhatsApp
web's internal QR component works.

cancelpairingcode now mirrors WhatsApp Web's resetLinkDeviceState:
refreshqr() restarts WebSocket for fresh refs, then
initializeqrlinking() generates a new key. QR emission happens
automatically via the existing change:ref listener.
@Adi1231234 Adi1231234 force-pushed the feature/cancel-pairing-code branch from 0c2b592 to 154d702 Compare March 20, 2026 05:43
Adi1231234 added a commit to Adi1231234/whatsapp-web.js that referenced this pull request Mar 20, 2026
- PR wwebjs#22449: dynamic ADV key via WAWebUserPrefsMultiDevice (closure bug fix),
  cancelPairingCode uses WAWebLaunchSocketUtils + WAWebAltDeviceLinkingApi
- PR wwebjs#201653: remove unnecessary try/catch around obj.off() and framenavigated
- PR wwebjs#182677: batch ciphertext PDO requests via pendingResend Set,
  remove try/catch from isPlaceholderMessageResendEnabled,
  add guard check in failTimer before emitting ciphertext_failed
- upstream: rename WAWebGroupQueryJob to WAWebGroupInviteJob in acceptInvite
Adi1231234 added a commit to Adi1231234/whatsapp-web.js that referenced this pull request Mar 20, 2026
- PR wwebjs#22449: dynamic ADV key via WAWebUserPrefsMultiDevice (closure bug fix),
  cancelPairingCode uses WAWebLaunchSocketUtils + WAWebAltDeviceLinkingApi
- PR wwebjs#201653: remove unnecessary try/catch around obj.off()
- PR wwebjs#182677: remove try/catch from isPlaceholderMessageResendEnabled
- upstream: rename WAWebGroupQueryJob to WAWebGroupInviteJob in acceptInvite
@sofi-ans
Copy link

@Adi1231234 @BenyFilho Works good 👍

@BenyFilho BenyFilho merged commit 3e810b6 into wwebjs:main Mar 24, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api changes API modifications approved Confirmed by maintainers typings Type definitions waiting for testers Needs testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: allow switching between QR code and pairing code without destroying the client

6 participants