open-wa
DocsError handling

Error handling

Handle runtime failures, create-time failures, and browser-side issues safely.

Error handling

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

  • restartOnCrash can help supervised runtimes restart predictably.
  • logConsole and logConsoleErrors help when the browser runtime is the real failure source.
  • killProcessOnBrowserClose is 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.

ErrorCommon code or signalLikely causeWhat to do
Auth timeoutAUTH_TIMEOUT, qrTimeout, authTimeout, repeated QR expiryThe 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 failureBROWSER_LAUNCH_FAILED, ENOENT, EACCES, sandbox errors, missing shared librariesChrome 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 warningLogin loops, forced logout, unusual activity prompts, send failures after high volumeWhatsApp 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 errorHTTP 401Missing 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 errorHTTP 403The 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 errorHTTP 429Too 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 errorHTTP 500The 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 qrTimeout expired.
  • 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:

  1. Restart the process and watch for a new QR code or link code.
  2. Keep the phone online and open WhatsApp while the runtime authenticates.
  3. Increase qrTimeout or authTimeout when a human operator needs more time.
  4. 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 useChrome or executablePath.
  • Remove a custom executablePath temporarily and let the selected driver use its default browser.
  • Run once with headless: false in 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.

These specific errors, especially as a crash loop shortly after the page loads, almost always mean one of:

  1. --single-process or --no-zygote in chromiumArgs. These crash modern Chrome on WhatsApp Web. The runtime strips them by default and logs Removed browser args known to crash WhatsApp Web; if you forced them with allowDangerousBrowserArgs: true, remove that. This is the most common cause in low-RAM Docker/Railway deployments.
  2. The renderer was OOM-killed. Check container memory limits and host dmesg / container OOM events. See the low-memory guidance on the Docker page.
  3. 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:

  1. Stop outbound automation for that account.
  2. Check the phone for warnings or necessary action.
  3. Reduce send volume, concurrency, and repeated identical message patterns.
  4. Prefer replies and expected conversations over cold outbound sends.
  5. Keep the same device, session, and network stable where possible.
  6. 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.

StatusMeaningCommon fix
401 UnauthorizedThe request did not include a valid API key.Pass the key as X-API-Key or through SocketClient.connect(baseUrl, apiKey).
403 ForbiddenThe 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 RequestsThe 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 ErrorThe 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 200 from /health with connected: true and session.ready: true means the API and WhatsApp session are ready. A successful /meta/basic/commands response confirms that the same URL and configured key can load command metadata.
  • HTTP 200 with connected: false or session.ready: false means the process is alive but authentication or runtime finalization is incomplete. Finish the QR or link-code flow, then inspect session.pending and session.blockers and check again.
  • HTTP 401 from /meta/basic/commands means the key is absent or does not match the process configuration. /health does not return 401 for a missing or incorrect key. Compare the value passed as --api-key (or its config source) with OPENWA_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_URL before 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 typeRetry strategy
Auth timeoutRestart the auth flow after checking phone availability. Do not run many parallel auth attempts for the same account.
Browser launch failureDo not retry blindly. Fix the browser path, dependencies, permissions, or container flags first.
Browser crash after readyRestart the runtime with the same sessionId. Let the saved session reconnect before deleting anything.
HTTP 401 or 403Do not retry until configuration is fixed. These are usually key, endpoint, or permission problems.
HTTP 429Retry with exponential backoff and jitter. Lower concurrency before resuming normal traffic.
HTTP 500Retry once or twice with a short delay. If it repeats, inspect logs and restart the session.
Blocked account riskDo 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>&1

For 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, and 500. 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.

On this page