Error handling
Handle runtime failures, create-time failures, and browser-side issues safely.
Treat every client method as an async operation and handle failures explicitly.
Embedded runtime pattern
import { Client, createClient } from '@open-wa/wa-automate';
import { PuppeteerDriver } from '@open-wa/driver-puppeteer';
const runtime = await createClient({
sessionId: 'sales',
driver: new PuppeteerDriver(),
headless: false,
});
const client = new Client({ client: runtime, transport: runtime.getTransport() });
await client.start();
runtime.events.on('message.received', async ({ message }) => {
try {
await client.sendText(message.from, 'Hi!');
} catch (error) {
console.error('sendText failed', error);
}
});createClient() creates the embedded runtime and client.start() begins the browser/session lifecycle. Keep those ownership boundaries together; use SocketClient when another Easy API process owns the browser.
Handling startup failures
async function launch() {
try {
const runtime = await createClient({
sessionId: 'sales',
driver: new PuppeteerDriver(),
headless: false,
});
const client = new Client({ client: runtime, transport: runtime.getTransport() });
await client.start();
const readiness = runtime.getReadiness();
if (!readiness.ready) throw new Error(`Session is not ready: ${readiness.pending.join(', ') || 'unknown blocker'}`);
return { runtime, client };
} catch (error) {
console.error('open-wa failed to start', error);
throw error;
}
}Operational helpers
restartOnCrashcan help supervised runtimes restart predictably.logConsoleandlogConsoleErrorshelp when the browser runtime is the real failure source.killProcessOnBrowserCloseis useful when an external supervisor is responsible for restart policy.
Browser/page errors
Because the runtime is browser-backed, you can also inspect page-level errors through the underlying page surface when your flow needs deeper debugging.
Common error catalogue
Use the catalogue below to separate startup problems, API problems, and WhatsApp-side risk signals before you retry or restart.
| Error | Common code or signal | Likely cause | What to do |
|---|---|---|---|
| Auth timeout | AUTH_TIMEOUT, qrTimeout, authTimeout, repeated QR expiry | The QR was not scanned in time, the link code expired, the phone was offline, or the session token is no longer valid. | Restart the session, keep the phone online, increase the timeout if humans need more time, and delete the session files only when the saved session is clearly invalid. |
| Browser launch failure | BROWSER_LAUNCH_FAILED, ENOENT, EACCES, sandbox errors, missing shared libraries | Chrome or Chromium cannot start, the configured executablePath is wrong, the container is missing browser dependencies, or the runtime lacks permissions. | Confirm the browser path, test with headless: false, install missing system packages, and use container flags that match your hosting environment. |
| Blocked account risk warning | Login loops, forced logout, unusual activity prompts, send failures after high volume | WhatsApp can limit or review the account because of automation patterns, message volume, recipient quality, or device changes. | Stop automation and review recent traffic. Decrease the send rate and reconnect from the phone. Do not delete sessions while the account is restricted. |
| API authentication error | HTTP 401 | Missing or wrong Easy API key. | Send the configured key with X-API-Key or the SocketClient connection key. Replace the key if it was exposed. |
| API forbidden error | HTTP 403 | The request is authenticated but not allowed for the current API surface, session state, or deployment boundary. | Check the endpoint, method, API key scope if applicable, and whether the session is ready. |
| API rate limit error | HTTP 429 | Too many HTTP calls or too much message activity in a short period. | Back off, add jitter, queue work, and lower concurrency. Do not retry immediately in a tight loop. |
| API server error | HTTP 500 | The runtime failed while handling the request, often because the browser, session, or WhatsApp Web state changed underneath it. | Capture logs, check session readiness, retry once after a short delay, then restart the runtime if the session is stuck. |
Auth timeout
Auth timeouts happen before the session becomes ready. The runtime is waiting for a QR scan, link-code confirmation, or saved session reconnect, and the expected state does not arrive before the configured timeout.
Common causes:
- The QR code was not scanned before
qrTimeoutexpired. - The link code expired before it was entered on the phone.
- The phone running WhatsApp is offline or has a weak connection.
- The saved session files are missing, stale, or corrupted.
- The account was logged out from WhatsApp > Linked Devices.
Recovery steps:
- Restart the process and watch for a new QR code or link code.
- Keep the phone online and open WhatsApp while the runtime authenticates.
- Increase
qrTimeoutorauthTimeoutwhen a human operator needs more time. - If the same saved session continues to fail, log out that linked device from the phone. Delete only that session's files, and authenticate again.
const runtime = await createClient({
driver: new PuppeteerDriver(),
sessionId: 'sales',
qrTimeoutMs: 0,
authTimeoutMs: 120_000,
});
const client = new Client({ client: runtime, transport: runtime.getTransport() });
await client.start();Use qrTimeoutMs: 0 only when you want the QR flow to wait without expiring in unattended or slow manual setup. For production, pair long auth windows with process supervision so a stuck startup can still be restarted intentionally.
Browser failures
Browser failures usually happen during createClient() or shortly after client.start(). The Node.js process is running, but the browser driver cannot launch, attach, navigate, or keep the WhatsApp Web page alive.
Check these first:
- Confirm Chrome or Chromium is installed if you set
useChromeorexecutablePath. - Remove a custom
executablePathtemporarily and let the selected driver use its default browser. - Run once with
headless: falsein a safe desktop environment so you can see the browser state. - In Linux containers, install the browser's necessary shared libraries and check sandbox permissions.
- Make sure only one process owns the same session directory at a time.
- Enable browser console logging when the page loads but WhatsApp Web fails inside the browser.
const runtime = await createClient({
driver: new PuppeteerDriver(),
sessionId: 'sales',
useChrome: true,
headless: false,
logConsole: true,
logConsoleErrors: true,
});If the browser closes after startup, treat it as a runtime failure rather than an auth failure. Restart the process with the same sessionId first. The saved session can usually reconnect without another QR scan unless WhatsApp invalidated it.
Navigating frame was detached / Attempted to use detached Frame
These specific errors, especially as a crash loop shortly after the page loads, almost always mean one of:
--single-processor--no-zygoteinchromiumArgs. These crash modern Chrome on WhatsApp Web. The runtime strips them by default and logsRemoved browser args known to crash WhatsApp Web; if you forced them withallowDangerousBrowserArgs: true, remove that. This is the most common cause in low-RAM Docker/Railway deployments.- The renderer was OOM-killed. Check container memory limits and host
dmesg/ container OOM events. See the low-memory guidance on the Docker page. - Too-small
/dev/shm. Give the container real shared memory (--shm-size=1gb) or keep--disable-dev-shm-usage.
Do not respond to detached-frame errors by adding more Chromium flags — the fix is almost always removing flags and giving the browser enough memory.
Blocked account risk
Some failures come from WhatsApp behavior rather than the open-wa process. Treat these as account risk signals, not bugs to retry through.
Signs include:
- WhatsApp Web repeatedly logs out a valid session.
- The phone shows unusual activity, spam, or verification prompts.
- Sends begin failing after a burst of outbound messages.
- New chats or unknown recipients fail more often than existing conversations.
- The same account works on the phone but becomes unstable as soon as automation resumes.
What to do:
- Stop outbound automation for that account.
- Check the phone for warnings or necessary action.
- Reduce send volume, concurrency, and repeated identical message patterns.
- Prefer replies and expected conversations over cold outbound sends.
- Keep the same device, session, and network stable where possible.
- Resume slowly only after the account behaves normally in the official app.
Do not try to solve account restriction by deleting session files in a loop. That can create more linked-device churn and make the account look less stable.
API HTTP errors
Easy API errors are HTTP responses from the local or remote API surface. They tell you whether the request reached the API and how the API classified the failure.
| Status | Meaning | Common fix |
|---|---|---|
401 Unauthorized | The request did not include a valid API key. | Pass the key as X-API-Key or through SocketClient.connect(baseUrl, apiKey). |
403 Forbidden | The API understood the request, but the current caller or runtime state is not allowed to do it. | Check the endpoint, deployment boundary, session readiness, and whether the method is exposed through that surface. |
429 Too Many Requests | The caller is sending too many requests or hitting a configured limit. | Back off with jitter, lower concurrency, and queue work per session. |
500 Internal Server Error | The API hit an unexpected runtime failure while handling the request. | Check logs, session state, browser health, and retry only after a short delay. |
For SocketClient consumers, handle command failures like normal async errors:
try {
await client.sendText(chatId, 'Hello');
} catch (error) {
console.error('Easy API command failed', error);
}Readiness and authentication checks
Run these checks from the same host that calls Easy API. /health is public: it
reports process and session readiness but does not check the API key. The
metadata request checks a configured key, so the two results separate an
unreachable process, a rejected key, and a live process whose WhatsApp session
is still authenticating:
export OPENWA_API_URL="http://127.0.0.1:8080"
export OPENWA_API_KEY="your-secure-key"
curl -sS -D - \
"$OPENWA_API_URL/health"
curl -sS -D - \
-H "X-API-Key: $OPENWA_API_KEY" \
"$OPENWA_API_URL/meta/basic/commands"Interpret the response before retrying a command:
HTTP 200from/healthwithconnected: trueandsession.ready: truemeans the API and WhatsApp session are ready. A successful/meta/basic/commandsresponse confirms that the same URL and configured key can load command metadata.HTTP 200withconnected: falseorsession.ready: falsemeans the process is alive but authentication or runtime finalization is incomplete. Finish the QR or link-code flow, then inspectsession.pendingandsession.blockersand check again.HTTP 401from/meta/basic/commandsmeans the key is absent or does not match the process configuration./healthdoes not return401for a missing or incorrect key. Compare the value passed as--api-key(or its config source) withOPENWA_API_KEY; do not keep retrying the same key.- A connection-refused or timeout error means the host, port, process, or network boundary is wrong. Start Easy API or correct
OPENWA_API_URLbefore investigating WhatsApp state.
Once readiness is true, make one low-risk send request and keep the response headers while diagnosing delivery:
curl -sS -D - -X POST \
-H "X-API-Key: $OPENWA_API_KEY" \
-H "Content-Type: application/json" \
"$OPENWA_API_URL/api/messages/sendText" \
-d '{"to":"447123456789@c.us","content":"readiness check","options":{}}'An HTTP 2xx response with a message result means the API accepted the
request. A 401 sends you back to key configuration, a 503 means the API
middleware is still waiting for a ready session, and a 4xx response usually
means the recipient ID or JSON body needs correcting. Do not turn a 200
health response into a send attempt until both readiness fields are true.
Retries
Retry based on the error type. A retry strategy that helps one class of failure can make another class worse.
| Error type | Retry strategy |
|---|---|
| Auth timeout | Restart the auth flow after checking phone availability. Do not run many parallel auth attempts for the same account. |
| Browser launch failure | Do not retry blindly. Fix the browser path, dependencies, permissions, or container flags first. |
| Browser crash after ready | Restart the runtime with the same sessionId. Let the saved session reconnect before deleting anything. |
HTTP 401 or 403 | Do not retry until configuration is fixed. These are usually key, endpoint, or permission problems. |
HTTP 429 | Retry with exponential backoff and jitter. Lower concurrency before resuming normal traffic. |
HTTP 500 | Retry once or twice with a short delay. If it repeats, inspect logs and restart the session. |
| Blocked account risk | Do not retry automatically. Stop automation and check the account from the phone. |
Example retry wrapper for transient API failures:
async function retryTransient(task: () => Promise<void>) {
const delays = [1000, 3000, 8000];
for (const delay of delays) {
try {
await task();
return;
} catch (error) {
console.error('Transient command failure', error);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
throw new Error('Command failed after retries');
}Keep retries idempotent where possible. Record the message intent before you send a message. This record prevents duplicate sends after a timeout.
Logging config
Turn on enough logging to see which layer failed: your app, Easy API, the browser driver, or WhatsApp Web inside the browser.
For Easy API, start by capturing stdout and stderr:
npx @open-wa/wa-automate@5.1.0 --session-id sales --host 127.0.0.1 --port 8080 --log-console --verbose > open-wa.log 2>&1For custom code, enable browser console logging during diagnosis:
const runtime = await createClient({
driver: new PuppeteerDriver(),
sessionId: 'sales',
logConsole: true,
logConsoleErrors: true,
});Log these fields around failed commands:
- session id
- method name
- request id or job id from your app
- chat id or recipient id when safe to store
- HTTP status code for Easy API calls
- whether the session was ready before the call
Do not log session tokens, API keys, complete message bodies, or personal data unless your compliance rules require it.
Recovery playbooks
Error type distinction
Handle each failure type separately.
- Runtime errors happen inside your Node.js process or the browser runtime. Examples include thrown client method errors, browser crashes, missing dependencies, and page errors.
- API errors are HTTP responses from Easy API. They have status codes such as
401,403,429, and500. The HTTP caller must handle them. - WhatsApp-level errors come from the connected account or WhatsApp Web behavior. Examples include logout, blocked sends, verification prompts, and account restriction warnings.
Handle runtime errors with process supervision and clear logs. Handle API errors with caller-side status handling. Handle WhatsApp-level errors conservatively, because aggressive retries can make account risk worse.
Was this helpful?
Your answer includes the page path and docs version.
