Skip to main content

Documentation Index

Fetch the complete documentation index at: https://zapo.to/llms.txt

Use this file to discover all available pages before exploring further.

zapo surfaces problems through three event channels:
  • connection with status: 'close' — the socket dropped, carrying a reason and optional code.
  • stream_failure — a stream-level failure, often just before a close.
  • stanza_error — a single request/stanza was rejected, without dropping the connection.
For the reconnection loop itself (backoff, isLogout, graceful shutdown), see Reconnection. This page is about understanding why something failed.

Disconnect reasons

The close event is { status: 'close', reason, code, isLogout }. reason is a WaDisconnectReason string; code is a numeric WaConnectionCode (or null). Use them to decide whether to reconnect:
client.on('connection', (event) => {
  if (event.status !== 'close') return
  if (event.isLogout || isFatal(event.reason)) {
    console.error('not reconnecting:', event.reason, event.code)
    return
  }
  void reconnect() // see the Reconnection guide
})

const FATAL = new Set([
  'stream_error_replaced',          // same credentials connected elsewhere
  'stream_error_device_removed',    // device unlinked
  'stream_error_force_logout',      // server forced logout (code 516)
  'failure_not_authorized',         // 401
  'failure_banned',                 // 406
  'failure_locked',                 // 403
  'failure_client_too_old',         // 405 — bump the advertised version
  'failure_bad_user_agent',         // 409
  'primary_identity_key_change'     // account identity changed — re-pair
])
const isFatal = (reason: string) => FATAL.has(reason)
stream_error_force_login (code 515) is not fatal — it’s a routine “reconnect now” the server sends right after pairing and occasionally during a session. Just reconnect with the stored credentials.
Transient reasons worth reconnecting on include stream_error_force_login, stream_error_ack, stream_error_xml_not_well_formed, stream_error_other, failure_service_unavailable, and comms_stopped. client_disconnected means you called disconnect() — expected, don’t reconnect.

Code reference

When present, code (on the close event and on stream_failure.failureCode) is one of:
CodeMeaningAction
401Not authorizedRe-pair
402Temporarily bannedBack off hard
403LockedStop
405Client too oldBump version
406BannedStop
409Bad user agentFix device fingerprint
500Internal server errorRetry later
503Service unavailableBack off and retry
515Reconnect requiredReconnect now
516Forced logoutRe-pair (isLogout: true)
The reason strings live in WA_DISCONNECT_REASONS and the 515/516 stream codes in WA_STREAM_SIGNALING, both exported from the package root. The numeric code values are typed as WaConnectionCode (also exported).

Stream failures

stream_failure (WaIncomingFailureEvent) carries the raw failure detail and usually precedes a disconnect — log it for context:
client.on('stream_failure', (event) => {
  console.warn('stream failure', {
    reason: event.failureReason,
    code: event.failureCode,
    message: event.failureMessage,
    url: event.failureUrl
  })
})

Error stanzas

stanza_error (WaIncomingErrorStanzaEvent) reports that a single stanza was rejected — for example a malformed query or a throttled request. The connection stays up:
client.on('stanza_error', (event) => {
  console.warn('stanza error', event.code, event.text)
})
A rejected client.message.send or client.lowlevel.query typically rejects its own promise too, so wrap individual calls in try/catch for per-operation handling; use stanza_error for visibility into errors that aren’t tied to a call you await.

See also

Reconnection

The backoff loop and isLogout handling.

Troubleshooting

Common pitfalls and quick answers.
Last modified on May 28, 2026