How to read group info 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 a group directory sync is an async generator over the paged list, a GET per group you want in detail, and a PATCH only when the subject or description in your data differs from what the API returned. The webhook keeps the cache fresh between runs, so the sync can run hourly rather than continuously.
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
List the groups this number is in
Page through the list and store the ids as strings. This list is the ground truth for which groups your automation can act on, and it changes whenever someone adds or removes the linked number.
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 list the groups with an async function* that fetches each page and yields the entries while the response carries a cursor; consume it with for await and keep every id as the string it arrived as. Skipping Number() on the id is the single most important line in this step.
API reference for this stepRead one group in detail
The group object carries the subject, description, creation time, current settings and the participant roll with roles. Fetch it before a write so you are acting on the present state rather than on a cached copy.
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 one group with a GET and keep the parsed object around; the subject, description and settings you compare against later all live on it. Check response.status for 404 rather than relying on a thrown error, since fetch will resolve a missing group like any other response.
API reference for this stepRename or re-describe the group
Subject and description changes are visible to every member as a system message, so make them deliberately. A description that carries the rules and an opt-out route does more for your ban risk than any clever pacing.
const url = 'https://api.wapito.com/v1/groups/120363041234567890@g.us'; const options = { method: 'PATCH', headers: {Authorization: 'Bearer wpt_YOUR_TOKEN', 'Content-Type': 'application/json'}, body: '{"subject":"Acme Launch Team","description":"Launch week: daily standup at 09:30."}' }; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); }In Node.js rename with fetch(url, { method: 'PATCH', headers, body: JSON.stringify(changes) }) where changes holds only the differing fields; an object diff computed in JavaScript decides whether the call happens at all. A 403 in response.status means the number is not an admin of that group.
API reference for this stepFollow changes over the webhook
Renames, description edits and setting changes made by any admin arrive as group events, which is how a cached directory of groups stays accurate without polling the list endpoint.
Arrives on your webhook as
groups.In Node.js the Express handler applies groups events to the cached row: payload.data carries the new subject, description or settings, and the group id keys the update. Return res.sendStatus(200) before the write or queue it, so a slow database never turns into webhook retries.
The whole script
Every step above in one runnable file. Save it as group-info.mjs, put your token in the environment, and run it.
// Group Info with the Wapito WhatsApp API.
//
// List the groups, read one in detail, rename or re-describe it when your own data changes, and follow edits on the webhook.
//
// Run it with:
// export WAPITO_TOKEN="wpt_..."
// node group-info.mjs
const BASE_URL = 'https://api.wapito.com/v1';
const TOKEN = process.env.WAPITO_TOKEN;
// --- List the groups this number is in ---
{
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);
}
}
// --- Read one group in detail ---
{
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);
}
}
// --- Rename or re-describe the group ---
{
const url = BASE_URL + '/groups/120363041234567890@g.us';
const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer ' + TOKEN, 'Content-Type': 'application/json'},
body: '{"subject":"Acme Launch Team","description":"Launch week: daily standup at 09:30."}'
};
try {
const response = await fetch(url, options);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
Receive the webhook
Express 5 receiver for groups — 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']);
/** 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
- An async generator that awaits fetch without a timeout can sit on one page indefinitely; pass signal: AbortSignal.timeout(30000) so the loop fails fast and the scheduler retries.
- Object spread into the PATCH body sends every field, and the API treats an unchanged subject as a write, which the group sees as a rename notice; diff first and send only what changed.
Frequently asked questions
Why is a group missing from the list?
Either the linked number is not in it, or the session has not finished syncing. A newly paired number receives its groups over a short period rather than instantly, so a directory built in the first minutes after pairing will be incomplete. Re-read it once the session reports itself healthy.
Can I read a group my number is not in?
Only its public metadata, and only if you hold an invite code for it - resolving a code returns the name, size and owner without joining. Beyond that, a group is invisible to a number that is not a member, which is a deliberate part of how WhatsApp works.
Does the group object include every participant?
It includes the participant roll with each member's role, which is what most automations need. For very large groups, prefer the dedicated participants endpoint so you can page through the membership instead of pulling one enormous object on every read.
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.