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.

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

typeTypeRequired fieldsGuide
textWaSendTextMessagetextSending messages
imageWaSendMediaMessagemedia, mimetypeMedia
videoWaSendMediaMessagemedia, mimetypeMedia
ptvWaSendMediaMessagemedia, mimetypeMedia
audioWaSendMediaMessagemedia, mimetypeMedia
documentWaSendMediaMessagemedia, mimetypeMedia
stickerWaSendMediaMessagemedia (mimetype optional)Media
sticker-packWaSendStickerPackMessagestickerPackId, name, publisher, stickers, trayIconMedia
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

typeTypeRequired fieldsGuide
reactionWaSendReactionMessageemoji, targetReactions
pollWaSendPollMessagename, optionsPolls
poll-voteWaSendPollVoteMessagepoll, selectedOptionNamesVoting
eventWaSendEventMessagename, startTimeEvents
event-responseWaSendEventResponseMessageevent, responseEvent response
pin / unpinWaSendPinMessagetargetPinning
keep / unkeepWaSendKeepMessagetargetKeep-in-chat
revokeWaSendRevokeMessagestanzaIdRevoking
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

FieldNotes
conversationPlain text.
extendedTextMessageText with context (links, mentions, replies). A non-empty matchedText makes it a link/media message.

Media

FieldResolved media type
imageMessageimage
videoMessagevideo (or gif when gifPlayback)
ptvMessageptv
audioMessageaudio (or ptt when ptt)
documentMessage / documentWithCaptionMessagedocument
stickerMessagesticker
stickerPackMessagesticker-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.

Location & contacts

// 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[] */] }
})
FieldResolved media type
locationMessagelocation (or live-location when isLive)
liveLocationMessagelive-location
contactMessagevcard
contactsArrayMessagecontact_array

Interactive & business

FieldResolved type
buttonsMessagebutton
buttonsResponseMessagebutton_response
listMessagelist
listResponseMessagelist_response
interactiveMessage (native flow)interactive
interactiveResponseMessagenative_flow_response
templateButtonReplyMessagetext
orderMessageorder
productMessageproduct
groupInviteMessageurl
await client.message.send(jid, {
  groupInviteMessage: {
    groupJid, inviteCode, inviteExpiration, groupName
  }
})

Polls & events (raw)

FieldResolved
pollCreationMessage / …V2 / …V3 / …V5poll (polltype: creation)
pollUpdateMessagepoll (polltype: vote)
pollResultSnapshotMessagetext
eventMessageevent (event_type: creation)
encEventResponseMessageevent (event_type: response)
Poll creation and event messages auto-persist their messageSecret so later votes/responses can be encrypted.

Protocol, edits & system

FieldNotes
protocolMessageRevokes (REVOKE), edits (MESSAGE_EDIT), ephemeral sync, welcome requests.
editedMessageEdited message wrapper (edit attr).
reactionMessage / encReactionMessageReaction (type: reaction); empty text revokes.
pinInChatMessagePin/unpin (edit: pin_in_chat).
keepInChatMessageKeep-in-chat.
encCommentMessageComment on a message.
requestPhoneNumberMessageRequest a phone number.
newsletterAdminInviteMessageNewsletter admin invite.
secretEncryptedMessageCarries secretEncType: EVENT_EDIT, POLL_EDIT, POLL_ADD_OPTION, MESSAGE_EDIT, MESSAGE_SCHEDULE.
messageHistoryNotice / messageHistoryBundleGroup 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' }
    }
  }
})
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
  }
})

Contact card (vCard)

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

Multiple contacts

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.
Last modified on May 27, 2026