Skip to content

Pairing extra numbers with the API

Pairing links the number’s WhatsApp to Turbo Notify as a linked device. After you add the extra number, whoever has its phone at hand finishes the pairing in WhatsApp, with a pairing code or by scanning a QR code. Through the API, you choose the method on each request. The dashboard offers the same two methods, see Pairing in the dashboard. The main number is paired in the dashboard.

If you offer WhatsApp numbers to your own customers, the person using the code or scanning the QR code is your customer, on your screen. This page shows how to build that screen so they can finish on their own, even when something goes wrong.

How it works

  1. You request the pairing with POST /v1/numbers/{ref}/pair, with the method pairing_code (the default) or qr. The number moves to status: "pairing".
  2. Within a few seconds the code, or the QR code image, is available in the pairing block of GET /v1/numbers/{ref}.
  3. On the number’s phone, the person opens WhatsApp. For the code, they go to Linked devices > Link a device > Link with phone number instead and type the code. For the QR code, they go to Linked devices > Link a device and point the camera at the image.
  4. The number moves to status: "connected" (or "failed", with the reason in reason).

Once paired, the number shows up under Linked devices on the phone with the name of your organization, so the person recognizes the connection and does not remove it by mistake. On WhatsApp Business for Android, the app asks the person to type a name for the device and shows that name instead.

Request the code or the QR code

The request body is optional: {"method": "pairing_code"} or {"method": "qr"}. Without a body, the method is pairing_code. Any other value answers 422 validation_error.

turbo-notify.sh
curl -X POST \
https://api.turbonotify.com/v1/numbers/vendas_sp/pair \
-H "Authorization: Bearer YOUR_KEY"

The example above requests the code (the default). To request the QR code, send method: "qr":

turbo-notify.sh
curl -X POST \
https://api.turbonotify.com/v1/numbers/vendas_sp/pair \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"method": "qr"}'

Required scope: numbers:write

HTTP status: 202
{
  "alias": "vendas_sp",
  "status": "pairing"
}

The response confirms the request. The code or the QR code is generated by WhatsApp and arrives asynchronously.

Show the code

Poll GET /v1/numbers/{ref} while status is "pairing". When the code is ready, the pairing block is filled in. Always read pairing.method before showing pairing.data:

HTTP status: 200
{
  "id": "num_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "contact_id": null,
  "name": null,
  "type": "extra",
  "alias": "vendas_sp",
  "number": "5511988887777",
  "status": "pairing",
  "at": "2026-09-28T14:00:00Z",
  "description": null,
  "scheduled_deletion_at": null,
  "reason": null,
  "pairing": {
    "method": "pairing_code",
    "data": "Z5X7-LTYL",
    "expires_at": "2026-09-28T14:02:40Z"
  }
}
  • pairing.method is "pairing_code".
  • pairing.data is the code, 8 characters, shown with a hyphen. Show it as text.
  • pairing.expires_at is when the whole attempt stops being usable: use it for the countdown.
  • While the code is still being generated, pairing is null.

When the countdown reaches zero without use, offer a button to ask for a new code or QR code. If nobody finishes, the number becomes failed with reason: "pairing_timeout" shortly after. The lookup requires the numbers:read scope.

If pairing.method is "qr", see Show the QR code.

Show the QR code

With method: "qr", the pairing block carries the QR code image:

HTTP status: 200
{
  "id": "num_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
  "contact_id": null,
  "name": null,
  "type": "extra",
  "alias": "vendas_sp",
  "number": "5511988887777",
  "status": "pairing",
  "at": "2026-09-28T14:00:00Z",
  "description": null,
  "scheduled_deletion_at": null,
  "reason": null,
  "pairing": {
    "method": "qr",
    "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
    "expires_at": "2026-09-28T14:02:40Z"
  }
}
  • pairing.method is "qr".
  • pairing.data is a PNG image as a data URI (data:image/png;base64,...). Use it as the src of an <img>, never as text.
  • pairing.expires_at is when the whole attempt stops being usable, as with the code: use it for the countdown.
  • WhatsApp renews the QR code on its own while the attempt lasts, and Turbo Notify keeps pairing.data current. Read GET /v1/numbers/{ref} again and replace the image on every read. Do not keep the first one.
  • While the QR code is still being generated, pairing is null.
qr.html
<img id="qr" alt="QR code to link WhatsApp" />
<script>
// result: what poll(alias) returns, every 2 seconds
if (result.screen === 'qr') document.getElementById('qr').src = result.image;
</script>

Mistyped code

If the code is mistyped on the phone, WhatsApp invalidates that code right away: not even the correct code is accepted afterwards. WhatsApp does not report this error to anyone, so your screen must offer a new code at any time.

To generate a new code, call POST /v1/numbers/{ref}/pair again while the number has status: "pairing". The previous code is discarded, the pairing block goes back to null and then carries the new code. Always show the latest code. The same applies when the code expires.

Replace the attempt in progress

Calling POST /v1/numbers/{ref}/pair while the number has status: "pairing" replaces the attempt in progress: a new code or QR code comes out, and you can switch the method (from code to QR code, or the other way around). It also works on an attempt started in the dashboard: the number’s sheet then follows the new attempt. The previous code or QR code stops working.

The WhatsApp of the registered number

Pairing must be completed in the WhatsApp of the registered number. If another WhatsApp uses the code or scans the QR code, the pairing is refused, the device is unlinked from that other WhatsApp and the number ends up with status: "failed" and reason: "paired_phone_mismatch". To try again, request a new code or QR code and complete the pairing on the phone of the registered number.

Pairing result

OutcomeThrough statusThrough webhook
Succeededstatus: "connected"number.connected
Failedstatus: "failed" and the reason in reasonnumber.connect.failed, with the same reason

The code and the QR code are only delivered by the status lookup: since they are short-lived credentials, they are not sent in webhooks. Show them only to the person pairing the number.

Failure reasons

reasonWhat to tell your customer
pairing_timeoutThe code or the QR code was generated but not used in time. Get a new code or QR code.
paired_phone_mismatchThe code or the QR code was used on another WhatsApp. Use the phone of the registered number.
phone_already_boundThis number is already in use in the account.
main_number_not_connectedThe account’s main number must be connected first. Reconnect the main number and try again.
code_generation_failedThe code or the QR code could not be obtained from WhatsApp right now. Try again shortly.
connection_lostThe number lost its connection to WhatsApp. Pair it again.
pairing_failedThe pairing could not be completed. Try again.

New reasons may be added. Treat a value you do not recognize as pairing_failed.

A pairing screen that handles the common cases on its own:

  1. Let the person choose the method and request the pairing with POST /v1/numbers/{ref}/pair: QR code when the customer sees the screen on a computer and points their phone at it, code when they open the screen on their own phone and type the code into WhatsApp.
  2. Poll the status every 2 seconds while status is "pairing". With pairing filled in, check pairing.method. If it is pairing_code, show pairing.data as text. If it is qr, show pairing.data as an image (<img src>) and replace the image on every read, because the QR code renews. In both cases, show the countdown to pairing.expires_at. With pairing null, show “generating”.
  3. Keep “Get a new code” or “Get a new QR code” always visible, not only when the attempt expires: it is the way out of a mistyped code. The button calls POST /v1/numbers/{ref}/pair again, and can switch the method.
  4. Finish on the result: inactive shows “finishing” for a moment; connected shows success; failed shows the message for the reason and offers to try again.
  5. Respect the attempt limit: on a 429, show how long is left from the Retry-After header instead of repeating the call.
pairing.js
const API = 'https://api.turbonotify.com/v1/numbers';
const headers = {
'Authorization': 'Bearer YOUR_KEY',
'Content-Type': 'application/json',
};
// Called when the screen opens and by the "Get a new code" or "Get a new QR code" button.
// method: 'pairing_code' or 'qr'.
async function requestPairing(alias, method) {
const res = await fetch(`${API}/${alias}/pair`, {
method: 'POST',
headers,
body: JSON.stringify({ method }),
});
if (res.status === 429) {
return { waitSeconds: Number(res.headers.get('Retry-After')) };
}
return { waitSeconds: 0 };
}
// Called every 2 seconds while the screen is open.
async function poll(alias) {
const res = await fetch(`${API}/${alias}`, { headers });
const number = await res.json();
if (number.status === 'connected') return { screen: 'success' };
if (number.status === 'failed') return { screen: 'failure', reason: number.reason };
if (number.status === 'inactive') return { screen: 'finishing' };
if (number.pairing) {
const { method, data, expires_at } = number.pairing;
// On the 'qr' screen, replace the <img> src on every read: the QR code renews.
if (method === 'qr') return { screen: 'qr', image: data, expiresAt: expires_at };
return { screen: 'code', code: data, expiresAt: expires_at };
}
return { screen: 'generating' };
}

Attempt limit

Each POST /v1/numbers/{ref}/pair asks WhatsApp for a new code, which triggers a notification on the number’s phone, or for a new QR code. Too many requests in a row make WhatsApp itself block new codes for that number for a while. To protect the number, pairing accepts up to 10 requests per minute per number and 30 per minute per account. Above that the response is 429 pairing_rate_limited, with the Retry-After header in seconds:

HTTP status: 429
  • Retry-After: 42
{
  "error": "pairing_rate_limited"
}

Cancel pairing

While the number has status: "pairing", you can cancel the request in progress and move the number back to "pending":

turbo-notify.sh
curl -X POST \
https://api.turbonotify.com/v1/numbers/vendas_sp/cancel-pairing \
-H "Authorization: Bearer YOUR_KEY"
HTTP status: 200
{
  "alias": "vendas_sp",
  "status": "pending"
}

Canceling only works while the number has status: "pairing". To pair later, request a new code or QR code.

Possible errors

Request the code or the QR code (POST /v1/numbers/{ref}/pair)

StatusError codeDescription
402subscription_inactiveThe organization’s subscription is not active (the response includes details.status).
402feature_not_availableThe current plan does not include extra numbers.
403main_number_pair_not_allowedThe main number is paired in the dashboard.
404number_not_foundThe alias or id does not exist in the account.
409already_paired_or_connectedThe number is already paired or connected.
422validation_errormethod is neither pairing_code nor qr (or the body carries another field).
429pairing_rate_limitedAttempt limit reached: wait Retry-After seconds.

Cancel (POST /v1/numbers/{ref}/cancel-pairing)

StatusError codeDescription
402feature_not_availableThe current plan does not include extra numbers.
404number_not_foundThe alias or id does not exist in the account.
409not_pairingThe number does not have status: "pairing".

Both routes also return 401 not_authenticated (invalid or missing key) and 403 insufficient_scope (missing the numbers:write scope).