Agencies

WhatsApp number rotation: how to spread the load across instances

When a client has several numbers, the question stops being how to send and becomes which number to send from. Pushing everything through one chip concentrates the risk and leaves the others idle. Rotation splits the volume, respects each number's cap and pulls out the ones that dropped. This guide shows the rule, a Node.js example on the documented endpoints and the limits of the method. The API has no ready-made balancer: rotation is your own logic on top of GET /instances and send/text. ZapZap is an unofficial solution, and WhatsApp may limit or block any number, even with the load well split.

Published on · 4 min read

This article is also available in: Português · Español

When rotation is worth it (and when it is not)

It fits operations where a single client talks to many people: a sales team with several lines, a clinic with several units, a store answering ad leads all day. Splitting the sending lowers the volume per number and keeps the operation alive when a chip drops.

It does not fit mass sends to people who never talked to you. Splitting a cold list across five numbers only spreads the problem over five chips, and consent is still what weighs most. If a number is new, warm it up first: see chip warm-up.

The four rules of a safe rotation

First, rotate only among connected numbers: read the instance list and filter by status. Second, a daily cap per number, set by you from each chip's phase (the operations sheet keeps that). Third, the same contact always uses the same number: someone who already talked to number A should not get the next message from number B, because the switch confuses the customer and looks suspicious to WhatsApp. Fourth, pause immediately any number that returns 429 or drops in status.

The third rule needs a simple table in your system: contact, source instance. Rotation applies only to new contacts. Contacts that already have an owner stay with it.

Code: rotation with a daily cap on the API

The example reads GET /instances, filters the connected ones (the status comes as CONNECTED in the reference's sample response), spins a cursor and skips any number that hit its cap. The counter lives in memory for the example only: in production keep it per day in a database, or the rotation resets on every restart. The send call is POST /{instanceId}/send/text with number and text. See the reference for the other fields.

Node.js: round-robin with a daily cap

// Round-robin sender: picks the next CONNECTED instance that is under its daily cap
const BASE = "https://api.zapzapapi.com/api/v1";
const H = {
  "Content-Type": "application/json",
  "x-api-key": process.env.ZAPZAP_API_KEY,
  "x-api-secret": process.env.ZAPZAP_API_SECRET,
};
const DAILY_CAP = 40;          // your own limit per number, from your operations sheet
const sentToday = new Map();   // instance id -> count (persist it in a database in production)
let cursor = 0;

async function connectedInstances() {
  const res = await fetch(BASE + "/instances", { headers: H });
  if (!res.ok) throw new Error("GET /instances failed: HTTP " + res.status);
  const list = await res.json();
  return list.filter((i) => i.status === "CONNECTED");
}

function pickInstance(pool) {
  for (let n = 0; n < pool.length; n++) {
    const inst = pool[(cursor + n) % pool.length];
    if ((sentToday.get(inst.id) || 0) < DAILY_CAP) {
      cursor = (cursor + n + 1) % pool.length;
      return inst;
    }
  }
  return null; // every number hit its cap: stop and try again tomorrow
}

export async function sendBalanced(number, text) {
  const pool = await connectedInstances();
  const inst = pickInstance(pool);
  if (!inst) throw new Error("no instance available under the daily cap");
  const res = await fetch(BASE + "/" + inst.id + "/send/text", {
    method: "POST",
    headers: H,
    body: JSON.stringify({ number, text }),
  });
  if (res.status === 429) throw new Error("rate limited, back off on " + inst.name);
  if (!res.ok) throw new Error("send failed on " + inst.name + ": HTTP " + res.status);
  sentToday.set(inst.id, (sentToday.get(inst.id) || 0) + 1);
  return { instance: inst.name, status: res.status };
}

Pace, 429 and drops: what the code does not solve alone

The reference lists 429 as rate limit exceeded. Treat it as a signal to back off on that number and carry on with the others, instead of retrying in a loop. If all of them hit the cap, the right answer is to stop and resume tomorrow, not to raise the cap.

Do not distribute identical bursts: vary the text, space the sends and prefer conversations to blasts. The API has per-message delay options, and the list send in /sender/simple accepts minimum and maximum delays; use them instead of sleeping in your own code for scheduled campaigns.

Cost and tracking

More numbers do not change the unit price: R$15 per active number per month, prepaid, with no per-message fee, so splitting the load across 4 numbers costs R$60 a month whatever the volume. Before creating more instances, check the cost at GET /instances/cost, which shows the activation fee and the balance needed.

Log, per number and per day: sent, replies and failures. A number whose reply rate falls while failures climb steps back a phase. For pricing at scale, see cost per number and volume and the pricing page. For who works this way, see AI agencies and traffic managers.

Quick summary

  • The API has no ready-made balancer: rotation is your own logic on top of GET /instances and send/text.
  • Rotate only among connected numbers, with a daily cap per number and the same contact always on the same number.
  • 429 and status drops take the number out of the queue at once; if all of them overflow, stop, do not raise the cap.
  • Splitting the load does not replace consent or warm-up, and the risk of the unofficial channel remains.
  • Cost is linear: R$15 per active number per month, with no per-message fee.

Also read

Frequently asked questions

Does ZapZap have ready-made number rotation in the API?

There is no rotation endpoint in the reference. Distribution happens in your system, choosing the instance before calling send/text, as in the guide's example.

What daily cap should I use per number?

The API does not publish a single cap. Set yours by chip phase: a new number talks only to people who already know the client, a mature number gets more volume gradually. See the warm-up guide.

Can I switch the number for someone who already talked to us?

Avoid it. The contact expects the same number, and the switch becomes noise and reports. Rotation is for new contacts.

Does splitting across numbers prevent blocks?

No. It reduces risk concentration, but ZapZap is an unofficial solution and WhatsApp may limit or block any number.

How much does it cost to run 5 numbers in rotation?

R$75 a month (5 x R$15), prepaid with no per-message fee, plus the one-time activation fee per instance created, which you check at GET /instances/cost.

Try rotation with two numbers

Create the account, connect two numbers and run the example. R$15 per active number per month, with no per-message fee. When you verify your phone there is a welcome credit, subject to the current campaign.