Handling Events

The socket is an event emitter (sock.ev). This is the complete event map of the package β€” every event name below exists in the BaileysEventMap type, with the payload it actually carries.

Connection & credentials

EventPayloadNotes
connection.updatePartial<ConnectionState>{ connection, lastDisconnect, qr, isNewLogin, receivedPendingNotifications } β€” see Connection & Pairing
creds.updatePartial<AuthenticationCreds>Always wire this to saveCreds from your auth state, or the session won't survive restarts

Messages

EventPayloadNotes
messages.upsert{ messages: WAMessage[], type, requestId? }type: 'notify' = new incoming messages in real time. type: 'append' = history/offline catch-up β€” most bots should react to notify only, or they'll reply to old messages after a reconnect
messages.updateWAMessageUpdate[]Status changes (sent β†’ delivered β†’ read), edits, and poll updates (update.pollUpdates)
messages.delete{ keys } | { jid, all: true }Messages deleted for everyone, or a whole chat cleared
messages.reaction{ key, reaction }[]Reactions added/removed (empty reaction.text = removed)
message-receipt.updateMessageUserReceiptUpdate[]Per-user receipts in groups: who read/played what, and when
messages.media-update{ key, media?, error? }[]Result of a media re-upload request (requestPlaceholderResend) β€” the new direct path for media that failed to download

Chats, contacts, presence

EventPayloadNotes
messaging-history.set{ chats, contacts, messages, isLatest?, progress?, syncType? }The big history sync after pairing β€” reverse-chronologically sorted. progress reports sync percentage
chats.upsert / chats.update / chats.deleteChat[] / ChatUpdate[] / string[]New chats, changed chats (name, unread count, archive state…), deleted chat JIDs
chats.phoneNumberShare{ lid, jid }A contact shared their phone number for a LID
contacts.upsert / contacts.updateContact[] / Partial<Contact>[]Address-book and profile changes
presence.update{ id, presences: { [participant]: PresenceData } }Only fires after sock.presenceSubscribe(jid). lastKnownPresence is one of available | unavailable | composing | recording | paused
blocklist.set / blocklist.update{ blocklist } | { blocklist, type: 'add' | 'remove' }The account's blocklist changed
labels.edit / labels.associationLabel / { label, type: 'add' | 'remove' }WhatsApp Business label changes

Groups & newsletters

EventPayloadNotes
groups.upsert / groups.updateGroupMetadata[] / Partial<GroupMetadata>[]Joined a group / group metadata changed
group-participants.update{ id, author, participants, action }action: add | remove | promote | demote | modify. The backbone of welcome/goodbye bots β€” see Recipes
group.join-request{ id, author, participant, action, method }Join requests when approval mode is on (action: 'created' | 'revoked' | …)
newsletter.view / newsletter-participants.update / newsletter-settings.update / newsletter.reactionnewsletter payloadsChannel views, subscriber changes, settings changes, and reactions to channel posts
callWACallEvent[]Incoming call offers/updates β€” reject with sock.rejectCall(callId, callFrom)

Reading an incoming message properly

sock.ev.on('messages.upsert', async ({ messages, type }) => {
  if (type !== 'notify') return // skip history catch-up
  for (const m of messages) {
    if (!m.message) continue          // key-only updates happen
    if (m.key.fromMe) continue        // don't loop on yourself
    const jid = m.key.remoteJid
    const isGroup = jid.endsWith('@g.us')
    const sender = isGroup ? m.key.participant : jid
    const text =
      m.message.conversation ||
      m.message.extendedTextMessage?.text ||
      m.message.imageMessage?.caption ||
      m.message.videoMessage?.caption || ''
    console.log(sender, ': ', text)
  }
})

Buffered processing (don't lose events)

The event emitter buffers events while your async handler is busy, if you use its processing helper β€” this exists so bursts (like the initial history sync) don't interleave half-written store updates:

const off = sock.ev.process(async (events) => {
  if (events['connection.update']) { /* ... */ }
  if (events['messages.upsert']) { /* ... */ }
})
// call off() to stop processing

And when you tear a socket down completely, call sock.ev.destroy() (a real destroy since v1.0.4) so no listeners leak into your next reconnect attempt.

Rule of thumb: if your bot "replies to old messages" or "answers twice after reconnecting", it's almost always an append upsert being treated as notify, or two sockets alive at once. Filter the type, and destroy the old emitter.