Documentation Index
Fetch the complete documentation index at: https://zapo.to/llms.txt
Use this file to discover all available pages before exploring further.
Everything you send goes through client.message.send(to, content, options?). The content argument is a WaSendMessageContent:
type WaSendMessageContent =
| string // shorthand for a text message
| WaSendTextMessage // type: 'text'
| WaSendReactionMessage // type: 'reaction'
| WaSendRevokeMessage // type: 'revoke'
| WaSendPinMessage // type: 'pin' | 'unpin'
| WaSendKeepMessage // type: 'keep' | 'unkeep'
| WaSendPollMessage // type: 'poll'
| WaSendPollVoteMessage // type: 'poll-vote'
| WaSendEventMessage // type: 'event'
| WaSendEventResponseMessage // type: 'event-response'
| WaSendMediaMessage // type: 'image' | 'video' | 'ptv' | 'audio' | 'document' | 'sticker' | 'sticker-pack'
| Proto.IMessage // raw protobuf — anything not covered above
There are two ways to send: a typed builder (an object with a type discriminator — the library validates and fills protocol fields for you) or a raw Proto.IMessage (you build the protobuf yourself). The same send() accepts both.
Shorthand
A plain string is sent as a text message:
await client.message.send(jid, 'Hello!')
Typed builders
Each builder is discriminated by its type field. Bold fields are required.
Text & media
type | Type | Required fields | Guide |
|---|
text | WaSendTextMessage | text | Sending messages |
image | WaSendMediaMessage | media, mimetype | Media |
video | WaSendMediaMessage | media, mimetype | Media |
ptv | WaSendMediaMessage | media, mimetype | Media |
audio | WaSendMediaMessage | media, mimetype | Media |
document | WaSendMediaMessage | media, mimetype | Media |
sticker | WaSendMediaMessage | media (mimetype optional) | Media |
sticker-pack | WaSendStickerPackMessage | stickerPackId, name, publisher, stickers, trayIcon | Media |
Media builders also accept any non-managed field of the underlying protobuf message (e.g. caption, gifPlayback, ptt, fileName) via the UserMediaFields mapping. Protocol-managed fields (url, mediaKey, fileSha256, directPath, …) are filled by the builder.
Interactive
type | Type | Required fields | Guide |
|---|
reaction | WaSendReactionMessage | emoji, target | Reactions |
poll | WaSendPollMessage | name, options | Polls |
poll-vote | WaSendPollVoteMessage | poll, selectedOptionNames | Voting |
event | WaSendEventMessage | name, startTime | Events |
event-response | WaSendEventResponseMessage | event, response | Event response |
pin / unpin | WaSendPinMessage | target | Pinning |
keep / unkeep | WaSendKeepMessage | target | Keep-in-chat |
revoke | WaSendRevokeMessage | stanzaId | Revoking |
target is a WaSendMessageTarget ({ stanzaId, fromMe, participant? }). poll/event parents additionally require authorJid and the 32-byte messageSecret.
Raw Proto.IMessage
For anything without a typed builder, pass a raw protobuf message. The library inspects the populated field and automatically resolves the stanza attributes — message type ([resolveMessageTypeAttr]), media type, polltype, event_type, view_once, and edit — so you only set the content field.
import { proto } from 'zapo-js'
await client.message.send(jid, {
conversation: 'A raw text message'
})
Text
| Field | Notes |
|---|
conversation | Plain text. |
extendedTextMessage | Text with context (links, mentions, replies). A non-empty matchedText makes it a link/media message. |
| Field | Resolved media type |
|---|
imageMessage | image |
videoMessage | video (or gif when gifPlayback) |
ptvMessage | ptv |
audioMessage | audio (or ptt when ptt) |
documentMessage / documentWithCaptionMessage | document |
stickerMessage | sticker |
stickerPackMessage | sticker-pack |
Raw media fields require pre-uploaded media (the encryption keys, directPath, and digests must already be set). To upload from bytes/a file, use the typed media builders instead — they perform the upload for you.
// Static location
await client.message.send(jid, {
locationMessage: { degreesLatitude: -23.55, degreesLongitude: -46.63, name: 'HQ' }
})
// Live location (resolved as `live-location`)
await client.message.send(jid, {
liveLocationMessage: { degreesLatitude: -23.55, degreesLongitude: -46.63 }
})
// Single contact (vCard)
await client.message.send(jid, {
contactMessage: { displayName: 'Maria', vcard: 'BEGIN:VCARD\n...\nEND:VCARD' }
})
// Multiple contacts
await client.message.send(jid, {
contactsArrayMessage: { displayName: 'Team', contacts: [/* IContactMessage[] */] }
})
| Field | Resolved media type |
|---|
locationMessage | location (or live-location when isLive) |
liveLocationMessage | live-location |
contactMessage | vcard |
contactsArrayMessage | contact_array |
Interactive & business
| Field | Resolved type |
|---|
buttonsMessage | button |
buttonsResponseMessage | button_response |
listMessage | list |
listResponseMessage | list_response |
interactiveMessage (native flow) | interactive |
interactiveResponseMessage | native_flow_response |
templateButtonReplyMessage | text |
orderMessage | order |
productMessage | product |
groupInviteMessage | url |
await client.message.send(jid, {
groupInviteMessage: {
groupJid, inviteCode, inviteExpiration, groupName
}
})
Polls & events (raw)
| Field | Resolved |
|---|
pollCreationMessage / …V2 / …V3 / …V5 | poll (polltype: creation) |
pollUpdateMessage | poll (polltype: vote) |
pollResultSnapshotMessage | text |
eventMessage | event (event_type: creation) |
encEventResponseMessage | event (event_type: response) |
Poll creation and event messages auto-persist their messageSecret so later votes/responses can be encrypted.
Protocol, edits & system
| Field | Notes |
|---|
protocolMessage | Revokes (REVOKE), edits (MESSAGE_EDIT), ephemeral sync, welcome requests. |
editedMessage | Edited message wrapper (edit attr). |
reactionMessage / encReactionMessage | Reaction (type: reaction); empty text revokes. |
pinInChatMessage | Pin/unpin (edit: pin_in_chat). |
keepInChatMessage | Keep-in-chat. |
encCommentMessage | Comment on a message. |
requestPhoneNumberMessage | Request a phone number. |
newsletterAdminInviteMessage | Newsletter admin invite. |
secretEncryptedMessage | Carries secretEncType: EVENT_EDIT, POLL_EDIT, POLL_ADD_OPTION, MESSAGE_EDIT, MESSAGE_SCHEDULE. |
messageHistoryNotice / messageHistoryBundle | Group history sharing. |
Wrappers
These wrap an inner message; the library unwraps them when resolving attributes:
ephemeralMessage, viewOnceMessage, viewOnceMessageV2, deviceSentMessage, groupMentionedMessage, botInvokeMessage, documentWithCaptionMessage.
For view-once specifically, prefer the viewOnce send option over hand-wrapping.
The full protobuf surface is available under the exported proto namespace — proto.Message, proto.Message.ProtocolMessage.Type, etc. Use it to build any field above and to reference enum values.
Raw proto cookbook
Concrete client.message.send(jid, …) payloads for the common raw kinds. Import proto for enum values:
import { proto } from 'zapo-js'
Plain text
await client.message.send(jid, { conversation: 'Hello' })
Text with mentions + reply
await client.message.send(jid, {
extendedTextMessage: {
text: 'Hey @5511999999999 👆',
contextInfo: {
mentionedJid: ['5511999999999@s.whatsapp.net'],
// quote/reply:
stanzaId: originalStanzaId,
participant: originalSenderJid,
quotedMessage: { conversation: 'the quoted text' }
}
}
})
Text with a manual link preview
await client.message.send(jid, {
extendedTextMessage: {
text: 'https://example.com',
matchedText: 'https://example.com',
title: 'Example',
description: 'Example domain'
}
})
Static location
await client.message.send(jid, {
locationMessage: {
degreesLatitude: -23.5505,
degreesLongitude: -46.6333,
name: 'São Paulo',
address: 'SP, Brazil'
}
})
Live location
await client.message.send(jid, {
liveLocationMessage: {
degreesLatitude: -23.5505,
degreesLongitude: -46.6333,
accuracyInMeters: 50,
speedInMps: 0,
caption: 'On my way',
sequenceNumber: 1
}
})
await client.message.send(jid, {
contactMessage: {
displayName: 'Maria Silva',
vcard: [
'BEGIN:VCARD',
'VERSION:3.0',
'FN:Maria Silva',
'TEL;type=CELL;waid=5511999999999:+55 11 99999-9999',
'END:VCARD'
].join('\n')
}
})
await client.message.send(jid, {
contactsArrayMessage: {
displayName: 'My contacts',
contacts: [
{ displayName: 'Maria', vcard: 'BEGIN:VCARD…END:VCARD' },
{ displayName: 'João', vcard: 'BEGIN:VCARD…END:VCARD' }
]
}
})
Group invite
await client.message.send(jid, {
groupInviteMessage: {
groupJid: '123456789-987654@g.us',
inviteCode: 'AbCdEf123',
inviteExpiration: Math.floor(Date.now() / 1000) + 86_400,
groupName: 'My group',
caption: 'Join us!'
}
})
Reaction (raw)
await client.message.send(jid, {
reactionMessage: {
key: { remoteJid: jid, fromMe: false, id: targetStanzaId, participant: senderJid },
text: '🔥', // empty string removes the reaction
senderTimestampMs: Date.now()
}
})
Pin / unpin (raw)
await client.message.send(jid, {
pinInChatMessage: {
key: { remoteJid: jid, fromMe: false, id: targetStanzaId },
type: proto.Message.PinInChatMessage.Type.PIN_FOR_ALL, // or UNPIN_FOR_ALL
senderTimestampMs: Date.now()
}
})
Keep / un-keep in chat (raw)
await client.message.send(jid, {
keepInChatMessage: {
key: { remoteJid: jid, fromMe: false, id: targetStanzaId },
keepType: proto.KeepType.KEEP_FOR_ALL, // or UNDO_KEEP_FOR_ALL
timestampMs: Date.now()
}
})
Revoke / delete-for-everyone (protocolMessage)
await client.message.send(jid, {
protocolMessage: {
key: { remoteJid: jid, fromMe: true, id: targetStanzaId },
type: proto.Message.ProtocolMessage.Type.REVOKE
}
})
Toggle disappearing messages (ephemeral setting)
await client.message.send(jid, {
protocolMessage: {
type: proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
ephemeralExpiration: 7 * 24 * 3600 // seconds; 0 disables
}
})
Poll (raw)
await client.message.send(jid, {
pollCreationMessage: {
name: 'Lunch?',
options: [{ optionName: 'Pizza' }, { optionName: 'Sushi' }],
selectableOptionsCount: 1
}
})
Request a phone number
await client.message.send(jid, { requestPhoneNumberMessage: {} })
Disappearing wrapper (ephemeralMessage)
Wrap any message so it inherits the chat’s ephemeral timer:
await client.message.send(jid, {
ephemeralMessage: { message: { conversation: 'This disappears' } }
})
View-once wrapper
await client.message.send(jid, {
viewOnceMessageV2: { message: { imageMessage: { /* pre-uploaded image fields */ } } }
})
Snippets that quote a message use a key of { remoteJid, fromMe, id, participant? } (a proto.IMessageKey). For the typed equivalents (reaction, revoke, pin, keep, poll) prefer the builders — they manage the message secret and key wiring for you.