WaClient is a strongly-typed event emitter. Every incoming activity — messages, receipts, group changes, presence — is surfaced as an event with a typed payload.
Plugins can contribute their own typed events; they appear on
client.on only when the plugin is in the plugins array. See the VoIP guide for an example — its voip_* events are available when voipPlugin() is installed.Listening
on, once, and off are all type-checked against the event map — the payload type is inferred from the event name, so listeners get full autocomplete.
Auth & connection
The
connection event is a discriminated union on status:
Messages
See Receiving messages for payload details and text extraction.
Unavailable messages
Some incoming<message> stanzas carry an <unavailable/> marker instead of an encrypted body — a view-once whose contents have already been consumed, a hosted/bot message the server could not fan out, or a plain fanout placeholder the primary device can still resend. There is nothing to decrypt on the stanza itself, but the arrival is useful information (audit logs, a “this message is no longer available” UI row, or waiting for the resend to land). The lib acks them and emits a typed message_unavailable event with a kind discriminator:
Resend requests are best-effort — like wa-web, they are not persisted, so a failed peer request is not retried. See Receiving messages → Recovering unavailable messages for the details.
Presence & chat-state
Groups, newsletters & profiles
State, history & MEX
MEX notification kinds
WaMexNotificationEvent is a discriminated union on kind. Every variant carries operationName (the upstream GraphQL operation) and errors: readonly WaMexNotificationGraphQlError[] (any GraphQL errors the server attached — typically empty).
*_hint kinds (username_update_hint, text_status_update_hint) carry only a contactHash, not the new value — the server is telling you “something changed for this bucket” and the client is expected to refetch through the regular profile / status fetch path.Companion host (mobile-primary)
Emitted by theclient.mobile coordinator when a mobile-primary session links, revokes, or fails to provision a companion device. All three only fire on a mobile-primary session.
Failures
Debug events
A family ofdebug_* events expose low-level internals — raw frames, decoded nodes, decode errors, unhandled stanzas, and client errors. They are useful for protocol debugging but noisy; subscribe selectively.
debug_decrypted_payload
The plaintext of every decrypted<enc> in the stanza, emitted between the unpad and the proto.Message.decode step. Fires whether or not decoding then succeeds — which is what makes the failing case observable at all. Without this hook, a payload that decrypts but does not decode (a proto field the library does not yet know about, a malformed body) is otherwise lost: decode throws, the stanza is reported through debug_unhandled_stanza, and the bytes go with it — while the decryption has already advanced the ratchet, so the same ciphertext will never decrypt again.
Mobile-registration events (
mobile_registration_code, mobile_account_takeover_notice) exist for the mobile-registration path and are not part of the standard companion flow.