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
})
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'
})
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' }
})