Skip to Content
Presence

Presence

client.presence exposes the four WhatsApp presence signals your bot can emit: marking the account as online or offline globally, and showing a “typing…” or “recording audio…” indicator inside a specific chat. It is a lazily-created PresenceModule — the object is constructed on first access and proxies every call through the live socket.

import { Client } from 'zaileys' const client = new Client() client.on('connect', async () => { await client.presence.online() const jid = '628xxxxxxxxxx@s.whatsapp.net' await client.presence.typing(jid, 2000) // shows "typing…", clears after 2 s await client.send(jid).text('Hey, thanks for reaching out!') })
🚫

Every method calls an internal requireSocket() guard. If the client is not connected when you call any presence method, it throws a ZaileysAutomationError with code NOT_CONNECTED and message client not connected. Always wait for the 'connect' event (or await client.connect()) before driving presence. See Error Handling.

Methods at a glance

MethodSignatureDescription
online()() => Promise<void>Marks the account as available (available) globally.
offline()() => Promise<void>Marks the account as unavailable (unavailable) globally.
typing(jid, ms?)(jid: string, ms?: number) => Promise<void>Shows a “typing…” indicator in the given chat. If ms is provided, auto-clears to paused after that many milliseconds.
recording(jid, ms?)(jid: string, ms?: number) => Promise<void>Shows a “recording audio…” indicator in the given chat. Auto-clears to paused after ms milliseconds if provided.

online()

online(): Promise<void>

Broadcasts an available status update globally, making your account appear online to contacts.

client.on('connect', async () => { await client.presence.online() })

offline()

offline(): Promise<void>

Broadcasts an unavailable status update globally. Use this to signal that the account is no longer active — for example, during scheduled downtime or before a clean shutdown.

process.on('SIGINT', async () => { await client.presence.offline() await client.disconnect() process.exit(0) })

typing(jid, ms?)

typing(jid: string, ms?: number): Promise<void>
ParameterTypeDescription
jidstringThe chat JID to show the indicator in (628xxxxxxxxxx@s.whatsapp.net for users, xxx@g.us for groups).
msnumber (optional)If provided, schedules an automatic paused clear after this many milliseconds.

Sends a composing presence update to the specified chat so the recipient sees “typing…”. When ms is given, a timer fires after that delay and sends paused to clear the indicator — you do not need to call anything else. The timer is unref’d so it will not prevent your process from exiting.

const jid = '628xxxxxxxxxx@s.whatsapp.net' // Manual clear (you are responsible for clearing later) await client.presence.typing(jid) // Auto-clear after 1.5 s await client.presence.typing(jid, 1500)

recording(jid, ms?)

recording(jid: string, ms?: number): Promise<void>
ParameterTypeDescription
jidstringThe chat JID to show the indicator in.
msnumber (optional)Auto-clears to paused after this many milliseconds, same as typing.

Shows a “recording audio…” indicator (recording) in the chat. Behaves identically to typing regarding auto-clear: pass ms to let zaileys clear it automatically.

const jid = '628xxxxxxxxxx@s.whatsapp.net' await client.presence.recording(jid, 3000) // clears after 3 s await client.send(jid).audio('https://example.com/voice-note.ogg', { ptt: true })

Auto-clear behavior

When you pass ms to typing or recording, zaileys schedules an internal setTimeout that sends a paused update to the same JID after the delay. This clears the indicator without any extra call from your side:

// Pattern: show indicator → wait the same delay → send reply const DELAY_MS = 2000 const jid = '628xxxxxxxxxx@s.whatsapp.net' await client.presence.typing(jid, DELAY_MS) setTimeout(async () => { await client.send(jid).text('Here is your answer.') }, DELAY_MS)

The auto-clear setTimeout is unref’d in Node.js — a pending clear will not keep your process alive on its own. If the socket disconnects before the timer fires, the clear is silently dropped (the sendPresenceUpdate error is caught and ignored internally).

Practical pattern: typing indicator inside a message handler

The most common use case is showing a “typing…” indicator before replying to an inbound message. Call typing with an ms value that matches how long your handler will actually take, then send the reply after that same delay:

import { Client } from 'zaileys' const client = new Client() client.on('text', async (ctx) => { const jid = ctx.roomId const THINK_MS = 1500 // Show "typing…" — auto-clears after THINK_MS await client.presence.typing(jid, THINK_MS) // Simulate processing time, then reply await new Promise((resolve) => setTimeout(resolve, THINK_MS)) await client.send(jid).text(`You said: ${ctx.text}`) })

For a voice-note bot the same pattern works with recording:

client.on('text', async (ctx) => { const jid = ctx.roomId await client.presence.recording(jid, 2000) await new Promise((resolve) => setTimeout(resolve, 2000)) await client.send(jid).audio('https://example.com/reply.ogg', { ptt: true }) })

Presence throttle

Built-in spam guard. Sending presence updates in rapid succession (e.g., calling typing inside a high-frequency loop) is a known signal that WhatsApp uses to identify bot accounts. The PresenceModule includes a built-in throttle that silently drops duplicate updates for the same (type, chat) pair within a configurable window.

How it works: the throttle is keyed per type + jid. The first call for a given key goes through immediately and records a timestamp. Any subsequent call for the same key within minIntervalMs milliseconds is dropped silently — the Promise resolves without sending. Once the window expires, the next call goes through and resets the timestamp.

Default: throttle is on with minIntervalMs: 1000 (1 second). This means calling typing(jid) in a tight loop sends at most one update per second per chat, regardless of how many times you call it.

Configure via the presence option in Configuration:

import { Client } from 'zaileys' // Tighten the window to 500 ms const client = new Client({ presence: { minIntervalMs: 500 }, }) // Disable the throttle entirely (not recommended in production) const client2 = new Client({ presence: { enabled: false }, })
OptionTypeDefaultDescription
enabledbooleantrueWhether the throttle is active.
minIntervalMsnumber1000Minimum milliseconds between two identical (type, jid) updates.

If you disable the throttle and drive presence from a hot path, you risk triggering WhatsApp’s rate-limiting or account restrictions. See Troubleshooting if your account gets flagged.

Error handling

Presence methods throw ZaileysAutomationError on failure. Import the class from zaileys to handle specific codes:

import { Client, ZaileysAutomationError } from 'zaileys' const client = new Client() const jid = '628xxxxxxxxxx@s.whatsapp.net' try { await client.presence.typing(jid) } catch (err) { if (err instanceof ZaileysAutomationError) { if (err.code === 'NOT_CONNECTED') { console.error('Client must be connected before driving presence.') } else if (err.code === 'PRESENCE_FAILED') { console.error('Presence update failed at socket level:', err.cause) } } }
CodeWhen it is thrown
NOT_CONNECTEDAny presence method is called before the client has an active socket.
PRESENCE_FAILEDThe underlying socket sendPresenceUpdate call throws. The original error is attached as err.cause.

See Error Handling for the full ZaileysAutomationError reference and general error-handling patterns.

See also

  • Automationclient.broadcast() and client.scheduleAt() for bulk and scheduled sends.
  • Configuration — the presence ClientOption and all other connection options.
  • Error HandlingZaileysAutomationError codes and catch patterns.
  • Troubleshooting — what to do if your account gets rate-limited or flagged.
Last updated on