How to leave a group in Node.js with the Wapito WhatsApp API
A script, a token and an HTTP client is the whole stack. Everything below runs from a file you can execute today, and the same code moves to a worker or a container without changing shape.
In Node.js the leave flow is a short script that reads more than it writes: an async generator over the group list, a GET per group to check the creator role, a promotion when needed, and a final POST that ends the number's membership. Keep the export step before the leave call in the same async function so the order cannot drift.
Before you start
npm install express (fetch is built in from Node 18)process.env.WAPITO_TOKEN- A channel with a WhatsApp number linked to it, and its API token. Create one in the dashboard — the authentication guide shows where the token goes.
How it works
Find the groups to leave
Work from the live list rather than from your own records, so an automation cleaning up after itself does not try to leave groups it was already removed from and log a pile of harmless 404s.
const url = 'https://api.wapito.com/v1/groups?count=50&offset=0'; const options = {method: 'GET', headers: {Authorization: 'Bearer wpt_YOUR_TOKEN'}}; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); }In Node.js consume the paged list with for await over an async generator and collect the ids into a Set of strings; intersect that with the ids you plan to leave using a filter on Set.has. Groups you were already removed from simply fail the membership test and never produce a request.
API reference for this stepHand over first if you are the creator
Read the roles. If the linked number is the group's creator, promote a human admin before leaving, otherwise the group is stranded with nobody able to change its settings or membership.
const url = 'https://api.wapito.com/v1/groups/120363041234567890@g.us'; const options = {method: 'GET', headers: {Authorization: 'Bearer wpt_YOUR_TOKEN'}}; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); }In Node.js read the group and find your own entry with participants.find(p => p.role === 'superadmin'); if it is the linked number, await a promotion POST for a named admin first and check response.ok on it. Only after that resolves truthy should the code fall through to the leave call.
API reference for this stepLeave the group
Leaving is immediate and announced to the group. The group becomes invisible to the linked number afterwards, so store anything you still need - the id, the membership, the transcript - before you call this.
const url = 'https://api.wapito.com/v1/groups/120363041234567890@g.us/leave'; const options = {method: 'POST', headers: {Authorization: 'Bearer wpt_YOUR_TOKEN'}}; try { const response = await fetch(url, options); console.log(response.status); } catch (error) { console.error(error); }In Node.js leave with fetch(url, { method: 'POST', headers }) on /groups/{id}/leave; there is no body. Because fetch does not throw on a 404, read response.status and treat it as the group having already dropped you. Do the export with fs.promises.writeFile before this call, awaited, so it cannot be lost.
API reference for this stepConfirm on the webhook
The departure arrives as a participant event, which is the signal to archive your own record rather than assuming the call succeeded and moving on.
Arrives on your webhook as
groups.participants.In Node.js the Express handler receives the participants event for the departure; match payload.data.participant against the linked number and mark the group archived in your store, then res.sendStatus(200). The event is the confirmation your script should wait for rather than the POST's status.
The whole script
Every step above in one runnable file. Save it as leave-group.mjs, put your token in the environment, and run it.
// Leave Group with the Wapito WhatsApp API.
//
// List what you are in, hand over the creator role if you hold it, leave, and archive your record when the webhook confirms it.
//
// Run it with:
// export WAPITO_TOKEN="wpt_..."
// node leave-group.mjs
const BASE_URL = 'https://api.wapito.com/v1';
const TOKEN = process.env.WAPITO_TOKEN;
// --- Find the groups to leave ---
{
const url = BASE_URL + '/groups?count=50&offset=0';
const options = {method: 'GET', headers: {Authorization: 'Bearer ' + TOKEN}};
try {
const response = await fetch(url, options);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
// --- Hand over first if you are the creator ---
{
const url = BASE_URL + '/groups/120363041234567890@g.us';
const options = {method: 'GET', headers: {Authorization: 'Bearer ' + TOKEN}};
try {
const response = await fetch(url, options);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
// --- Leave the group ---
{
const url = BASE_URL + '/groups/120363041234567890@g.us/leave';
const options = {method: 'POST', headers: {Authorization: 'Bearer ' + TOKEN}};
try {
const response = await fetch(url, options);
console.log(response.status);
} catch (error) {
console.error(error);
}
}
Receive the webhook
Express 5 receiver for groups, groups.participants — it verifies the signature, answers immediately and does the work after. Install it with npm install express and save it as webhook.mjs.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const SECRET = process.env.WAPITO_WEBHOOK_SECRET;
const EVENTS = new Set(['groups', 'groups.participants']);
/** Checks the X-Wapito-Signature header: t=<ms>,v1=<hex hmac-sha256>. */
function verify(raw, header) {
if (!header) return false;
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (!parts.t || !parts.v1) return false;
if (Math.abs(Date.now() - Number(parts.t)) > 300_000) return false; // 5 minute clock skew
const expected = crypto.createHmac('sha256', SECRET).update(`${parts.t}.`).update(raw).digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(parts.v1, 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post('/wapito', express.raw({ type: 'application/json' }), (req, res) => {
if (!verify(req.body, req.get('X-Wapito-Signature'))) return res.sendStatus(401);
const payload = JSON.parse(req.body.toString('utf8'));
if (EVENTS.has(payload.event)) console.log(payload.event, payload.data);
res.sendStatus(200); // answer 2xx fast; do the real work in a queue
});
app.listen(Number(process.env.PORT ?? 3000));
Errors you may hit
Gotchas
- fetch never rejects on a 4xx or 5xx. Check response.ok yourself; an await fetch(...) that "worked" can still be a 402 plan_required with a JSON error body.
- Do not let express.json() run before your webhook route. Signature verification needs the exact raw bytes, so mount express.raw({ type: "application/json" }) on the webhook path and parse the JSON yourself afterwards.
- Node 18+ has global fetch, but node-fetch v2, undici and axios all disagree about how they surface non-2xx responses. Pick one client per project or your error handling will be inconsistent.
- Promise.all over a list of recipients defeats the send queue: the API will serialise them anyway and return 429 send_rate_limited for the overflow. Send sequentially with await, or use a queue with concurrency 1 per channel.
- Large numbers in JSON.parse lose precision. Treat every WhatsApp id, group id and timestamp that arrives as a string as a string - never Number() it.
Where to run it
- Google Cloud Run (container, min instances 0, one process per channel worker)
- Railway or Render web service for the Express receiver
- Vercel or Netlify Functions for the webhook receiver only - they cannot hold the sender queue open
- Fly.io machine with a persistent volume if you queue sends locally
Pitfalls in Node.js
- A Promise.all over the leave calls fires them concurrently and skips the creator check ordering; run the promote-then-leave pair for each group in sequence inside a for...of loop.
- Once the POST succeeds the group id no longer resolves, so any code after it that calls GET /groups/{id} to log the final state gets a 404 that fetch reports as a normal response and your log shows an empty group.
- Writing the export with fs.writeFile without awaiting its promise lets the leave call run before the file is on disk; use await fs.promises.writeFile.
Frequently asked questions
Can I rejoin a group I left?
Only with a fresh invite link or by being added by an admin. There is no undo, and the group's history from before you left does not come back with you. If a bot might need to return, keep the group id and make sure a human admin can re-invite it.
Do other members see that I left?
Yes. Departures appear as a system message in the group, the same as joins. If your automation leaves a customer-facing group, consider posting a short handover note first so people know where to direct follow-up questions rather than replying into a thread nobody is watching.
What happens to messages I sent before leaving?
They stay in the group for everyone else, exactly as they were - leaving retracts nothing at all. If a message needs removing, delete it before you leave, and remember that WhatsApp only allows deletion for everyone within a limited window after the message was sent. After that window, and after you have left, the message is simply part of the group history.
Related
Try it on your own number
Create a channel, link a WhatsApp number by QR or pairing code, and call the API in a couple of minutes. The Sandbox plan is free and needs no card.