Skip to content

Configuration ​

ts
const wa = new WhatsAppClient({
  token: "EAAG...",
  phoneId: "1234567890",
  wabaId: "9876543210",
  apiVersion: "v25.0",
  timeoutMs: 30_000,
  retry: { retries: 3, minDelayMs: 500, maxDelayMs: 10_000 },
  logger: console,
});
OptionTypeDefaultDescription
tokenstringrequiredAccess token
phoneIdstringrequiredPhone Number ID
wabaIdstring—WhatsApp Business Account ID. Required for template methods.
apiVersionstring"v25.0"Graph API version
baseUrlstringhttps://graph.facebook.comOverride for proxies or mock servers
timeoutMsnumber30000Per-request timeout. 0 disables it.
retryRetryOptions | falsesee belowBackoff policy
fetchtypeof fetchglobalThis.fetchCustom fetch implementation
loggerWhatsAppLoggersilentReceives debug (each request) and warn (each retry)

Retries ​

The client retries a request only when retrying is safe:

SituationRetried?
Rate limit: codes 4, 80007, 130429, 131056 or HTTP 429✅ Any method
5xx, network error or timeout on GET✅
5xx, network error or timeout on POST/DELETE❌ The message may already have been sent
131048 (spam rate limit / messaging tier exhausted)❌ Retrying minutes later won't help
Any other API error❌

Delays grow exponentially (minDelayMs × 2^attempt, capped at maxDelayMs) with jitter, so parallel requests don't retry in lockstep.

ts
// Disable retries entirely
new WhatsAppClient({ token, phoneId, retry: false });

Per-call options ​

Every method accepts { signal, timeoutMs } as its last argument:

ts
const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000);

await wa.getTemplates({ signal: controller.signal });
await wa.sendText(to, "hi", { timeoutMs: 5_000 });

Aborting also cancels any pending retry delay.

Logging ​

Nothing is logged by default. Pass any object with optional debug / warn methods:

ts
new WhatsAppClient({ token, phoneId, logger: console });

// pino
const log = pino();
new WhatsAppClient({
  token,
  phoneId,
  logger: {
    debug: (msg, meta) => log.debug(meta, msg),
    warn: (msg, meta) => log.warn(meta, msg),
  },
});

The access token is never passed to the logger, and query strings of signed media URLs are stripped.

Custom fetch ​

Useful for corporate proxies, tests, or adding tracing:

ts
import { ProxyAgent, fetch as undiciFetch } from "undici";

const dispatcher = new ProxyAgent("http://proxy.internal:8080");
new WhatsAppClient({
  token,
  phoneId,
  fetch: (url, init) => undiciFetch(url, { ...init, dispatcher }),
});

Security ​

The access token always travels in the Authorization header and never in the URL, so it can't end up in proxy logs, CDN logs or error trackers.

Released under the MPL-2.0 License. Not affiliated with Meta Platforms, Inc. or WhatsApp LLC.