open-wa v5 is alpha. Use v4.76.0 for mature production systems unless you are validating v5.
The Client APIAPI ExplorerLicensing

Session events

Capture QR codes, session data, and launch lifecycle events with the shared event emitter.

Event Wally

Session events

When you need lifecycle visibility outside the client convenience methods, use the shared event emitter ev.

import { ev } from '@open-wa/wa-automate';

Full event list

The public session lifecycle events are small and focused. Some names are emitted directly by the core event bus, while a few are compatibility or plugin hook names that map to lower-level launch events.

EventWhen it firesPayload
core.startingThe runtime is about to start.{ config: unknown }
core.startedThe runtime has finished startup.{}
core.stoppingThe runtime is shutting down.{ reason?: string }
client.readyWhatsApp is authenticated and the client is ready for commands.{ sessionId: string }
client.disconnectedA compatibility name for a disconnected session. In the core event map, listen for session.connection.disconnected.{ sessionId?: string; reason?: string; wasLoggedIn?: boolean }
auth.qrA plugin hook name for QR generation. In the core event map, this is launch.auth.qr.generated.{ sessionId: string; qr: string; attempt: number }
auth.authenticatedAuthentication has succeeded. The plugin hook maps to launch.auth.check.after when isAuthenticated is true.{ sessionId: string }
auth.failureAuthentication failed or timed out. If your integration emits this compatibility event, treat it as an auth error signal.{ sessionId?: string; reason?: string; error?: unknown }
session.dataLegacy session-data style event, if your runtime or migration path emits it. Older examples use sessionData.**.{ sessionId: string; sessionData: unknown }

For low-level launch tracing, you can also listen to the internal launch.auth.* and session.connection.* events. Prefer the public names above unless you are debugging startup behavior.

Payload shapes

Core lifecycle events use direct payload objects:

type CoreStartingPayload = {
  config: unknown;
};

type CoreStartedPayload = Record<string, never>;

type CoreStoppingPayload = {
  reason?: string;
};

type ClientReadyPayload = {
  sessionId: string;
};

Auth and session events usually include the session id so you can route the event safely:

type AuthQrPayload = {
  sessionId: string;
  qr: string;
  attempt: number;
};

type AuthAuthenticatedPayload = {
  sessionId: string;
};

type AuthFailurePayload = {
  sessionId?: string;
  reason?: string;
  error?: unknown;
};

type ClientDisconnectedPayload = {
  sessionId?: string;
  reason?: string;
  wasLoggedIn?: boolean;
};

type SessionDataPayload = {
  sessionId: string;
  sessionData: unknown;
};

The low-level launch.* events use a step-event shape with tracing fields:

type StepEvent<TDetails = Record<string, unknown>> = {
  correlationId: string;
  attempt?: number;
  ts: number;
  step: string;
  details?: TDetails;
  error?: {
    name: string;
    message: string;
    stack?: string;
  };
  durationMs?: number;
};

For example, launch.auth.qr.generated carries the QR inside details:

ev.on('launch.auth.qr.generated', (event) => {
  const qr = event.details?.qr;
  const attempt = event.details?.attemptInThisCycle;
});

Timing

A first login usually follows this order:

  1. core.starting, the runtime has accepted the config and is launching.
  2. auth.qr or launch.auth.qr.generated, a QR is available for the user to scan.
  3. auth.authenticated or launch.auth.check.after, WhatsApp accepted the login.
  4. client.ready, the client can now send commands and receive messages.
  5. core.started, startup has completed.

If the session is already authenticated on disk, the QR step can be skipped. If WhatsApp disconnects later, listen for session.connection.disconnected or your integration's client.disconnected compatibility event.

Capture QR codes

import { ev } from '@open-wa/wa-automate';
import fs from 'node:fs';

ev.on('qr.**', async (qrcode) => {
  const imageBuffer = Buffer.from(
    qrcode.replace('data:image/png;base64,', ''),
    'base64',
  );

  fs.writeFileSync('qr_code.png', imageBuffer);
});

Capture session data

ev.on('sessionData.**', async (sessionData, sessionId) => {
  // store or encrypt the latest auth payload for your own workflow
});

QR/sessionData security

QR codes and session data are sensitive auth material. Anyone who can read them may be able to connect or restore a WhatsApp session.

Keep these rules strict:

  • Don't log QR strings, sessionData, or base64 session payloads.
  • Don't send them to analytics, crash reporting, or shared support channels.
  • Store session material only when you need it, and encrypt it at rest.
  • Redact these fields before forwarding wildcard event payloads to webhooks or logs.
  • Rotate or delete stored session data when a device is lost, an employee leaves, or a session is no longer trusted.

Listen to launch namespaces

Event names are typically shaped like:

namespace.sessionId

Examples:

ev.on('qr.**', () => {});
ev.on('sessionData.**', () => {});
ev.on('**.session1', () => {});
ev.on('**.**', (data, sessionId, namespace) => {
  console.log(namespace, sessionId, data);
});

Use wildcard subscriptions carefully in multi-session environments so you do not lose track of which session produced the event.

Multi-session examples

When you run more than one session, keep every event tied to a sessionId.

import { ev } from '@open-wa/wa-automate';

const sessions = new Map<string, { ready: boolean; lastQrAttempt?: number }>();

ev.on('qr.**', async (qrcode, sessionId) => {
  const state = sessions.get(sessionId) ?? { ready: false };
  sessions.set(sessionId, {
    ...state,
    lastQrAttempt: (state.lastQrAttempt ?? 0) + 1,
  });

  await deliverQrToTheRightOwner(sessionId, qrcode);
});

ev.on('client.ready', ({ sessionId }) => {
  sessions.set(sessionId, { ready: true });
});

ev.on('**.**', (event, sessionId, namespace) => {
  if (namespace !== 'session.connection.disconnected') return;

  sessions.set(sessionId, { ready: false });
});

If you use wildcard events, always include sessionId in the data you forward:

ev.on('**.**', (data, sessionId, namespace) => {
  auditEvent({
    sessionId,
    namespace,
    kind: 'open-wa-session-event',
  });
});

ev vs client listener

Use ev for process-level lifecycle events. It can see startup, authentication, QR generation, disconnects, session data, and other runtime signals that may happen before a client is ready.

Use client listeners for WhatsApp activity after the client is connected:

client.onMessage(async (message) => {
  if (message.body === 'ping') {
    await client.sendText(message.from, 'pong');
  }
});

The split is practical:

  • ev answers questions about the runtime, such as "did auth start?", "which session emitted a QR?", or "why did this session disconnect?"
  • client.onMessage and similar listeners answer questions about WhatsApp traffic, such as "what message arrived?" or "what should the bot send back?"

Wildcard patterns

The shared emitter supports wildcard subscriptions. Use them when you need to subscribe across sessions or namespaces.

// Every QR event for every session.
ev.on('qr.**', (qrcode, sessionId) => {});

// Every session-data event for every session, if emitted by your runtime.
ev.on('sessionData.**', (sessionData, sessionId) => {});

// Every namespace for one session.
ev.on('**.sales', (data, sessionId, namespace) => {});

// Everything. Keep this for debugging or carefully redacted audit sinks.
ev.on('**.**', (data, sessionId, namespace) => {});

Wildcard listeners are convenient, but they can capture sensitive data. Filter by namespace, redact before logging, and avoid catch-all listeners in production unless you have a clear retention policy.

Wally the Walrus typing

Was this helpful?

Wally and his cute companion coffee mug are coding day and night to keep this up-to-date!

On this page