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.

Beyond text and media, client.message.send accepts a family of typed interactive content objects. Each is discriminated by its type field.

Targeting a message

Several of these reference an existing message via a WaSendMessageTarget:
interface WaSendMessageTarget {
  stanzaId: string      // the id of the message you're targeting
  fromMe: boolean       // was the target sent by you?
  participant?: string  // required in groups when targeting someone else's message
}
You typically build it from an incoming event:
const target = {
  stanzaId: event.stanzaId!,
  fromMe: event.isSender ?? false,
  participant: event.senderJid
}

Reactions

await client.message.send(jid, {
  type: 'reaction',
  emoji: '👍',
  target
})
Pass an empty string as emoji to remove a previous reaction:
await client.message.send(jid, { type: 'reaction', emoji: '', target })

Polls

const result = await client.message.send(jid, {
  type: 'poll',
  name: 'Lunch?',
  options: ['Pizza', 'Sushi', 'Salad'],
  selectableCount: 1,        // how many options a voter may pick
  allowAddOption: false
})
Options may be plain strings or { name } objects. Order matters — it is used for vote hashing.

Voting on a poll

Voting requires the original poll’s identity and its messageSecret (32 bytes from the poll’s messageContextInfo.messageSecret):
await client.message.send(jid, {
  type: 'poll-vote',
  poll: {
    stanzaId: pollStanzaId,
    fromMe: false,
    authorJid: pollAuthorJid,
    messageSecret: pollMessageSecret, // Uint8Array, 32 bytes
    participant: pollAuthorJid        // required outside 1:1 chats
  },
  selectedOptionNames: ['Pizza']      // exactly as they appeared in the poll
})
Incoming votes arrive as message_addon events once decrypted.

Editing a message

To edit, send the new content and pass editKey in the options. The original must be fromMe:
await client.message.send(jid, 'Corrected text', {
  editKey: {
    stanzaId: originalStanzaId,
    // participant required in groups for lid/pn-addressed originals
    participant: undefined
  }
})
The new payload is wrapped in a MESSAGE_EDIT protocol message targeting editKey.stanzaId.

Revoking (delete for everyone)

await client.message.send(jid, {
  type: 'revoke',
  stanzaId: targetStanzaId,
  fromMe: true,
  // participant required when an admin revokes someone else's message in a group
  participant: undefined
})

Pinning

await client.message.send(jid, { type: 'pin', target })   // pin
await client.message.send(jid, { type: 'unpin', target }) // unpin

Keep-in-chat

For disappearing-message chats, keep (or un-keep) a specific message:
await client.message.send(jid, { type: 'keep', target })
await client.message.send(jid, { type: 'unkeep', target })

Events

Create a calendar-style event message:
await client.message.send(groupJid, {
  type: 'event',
  name: 'Team sync',
  description: 'Weekly catch-up',
  startTime: Math.floor(Date.now() / 1000) + 3600, // unix seconds
  location: { latitude: -23.5, longitude: -46.6, name: 'HQ' },
  joinLink: 'https://meet.example.com/abc',
  hasReminder: true,
  reminderOffsetSec: 600
})

Responding to an event

await client.message.send(jid, {
  type: 'event-response',
  event: {
    stanzaId: eventStanzaId,
    fromMe: false,
    authorJid: eventAuthorJid,
    messageSecret: eventMessageSecret // 32 bytes
  },
  response: 'going' // 'going' | 'not_going' | 'maybe'
})

Locations & contacts

There is no dedicated builder for static locations or contact cards yet — send them as a raw Proto.IMessage:
await client.message.send(jid, {
  locationMessage: { degreesLatitude: -23.5, degreesLongitude: -46.6 }
})

await client.message.send(jid, {
  contactMessage: { displayName: 'Jane', vcard: 'BEGIN:VCARD\n...\nEND:VCARD' }
})
Last modified on May 27, 2026