POST with a JSON body in the same envelope. The top-level
fields never change; only data varies by event type.
Envelope
string
Event type. One of the values in Event types.
string
ID of the event. Use it as an idempotency key — delivery is at-least-once.
- It holds the same value as the
webhook-idheader and does not change across retries of the same event. - If several webhooks of the instance are subscribed to the event, they all receive
the same
id. - Different events have different
ids — including theDELIVEREDand theREADof the same message. - It is an opaque value: do not extract information from it. To refer to the
message, use
data.messageId.
string
ID of the Zapfy instance (the connected number) that produced the event.
string
When the event happened, ISO 8601 in UTC, with second resolution. For messages and
statuses it is the time recorded by WhatsApp; for connection and QR Code events,
the time Zapfy registered the event. It stays the same across retries.
object
Event-specific payload, described below.
Event types
These are also the names you use in
events when
registering a webhook.
Conventions
- A field without a value is omitted. It never comes as
nullor as an empty string: check whether it is present (if (data.quotedMessageId)). Each field below says when it is present. - Type-specific objects (
media,location,reaction…) appear only in the message type they belong to. - Enum values (
DIRECT,READ,BANNED…) are uppercase and part of the contract. - Phone numbers are digits only, with the country code and no
+(5511999998888). They are the number exactly as WhatsApp has it registered, which is not always the way you typed it — see the note below.
Common objects
chat
The conversation: where the event happened and where you reply.
- In a direct conversation,
chatis the contact: inMESSAGE.RECEIVED, the contact who wrote; inMESSAGE.SENT, the contact you sent to. - In a group,
chatis the group.
string
DIRECT (a person) · GROUP · STATUS (status updates) · BROADCAST (broadcast
list) · NEWSLETTER (channel).string
The conversation’s WhatsApp identifier. Pass it as
to to reply — the API
accepts it as is. Always in the same format:
5511988887777@s.whatsapp.net (a person), 81896604192873@lid (a person whose
number WhatsApp hides), 120363012345678901@g.us (a group — the same id returned
by GET /v1/groups), status@broadcast (status updates), …@newsletter (a
channel). Use it as the key of the conversation in your system.string
The person’s phone, digits only. Present only in
DIRECT conversations, and only
when WhatsApp exposes the number. Use it to filter or match by number.sender
Who wrote the message, when someone else wrote it: in a direct conversation it is
the contact themself; in a group, the participant who wrote.
sender is absent when fromMe is true — in direct conversations and in
groups. A message you wrote is always from your own number, so fromMe is all you
need.
string
The WhatsApp identifier of who wrote, in the same format as
chat.id
(…@s.whatsapp.net, or …@lid when WhatsApp hides the number). Accepted in to.string
The person’s phone. Present only when WhatsApp exposes the number.
string
The profile name the contact set on their own WhatsApp — not the name saved in your
contacts. Present only when the contact has a profile name.
WhatsApp hides the phone number of some contacts. When that happens,
phone is
absent and id is an …@lid identifier, which still works as to. To filter by
number, use phone, never id.MESSAGE.RECEIVED / MESSAGE.SENT
Text in a direct conversation:string
Message ID on WhatsApp — the same value the send endpoints return. Use it to react,
edit, delete, quote and to match
MESSAGE.STATUS_UPDATED.boolean
true in MESSAGE.SENT (your number wrote it), false in MESSAGE.RECEIVED.string
Content type. See the table below.
string
The text of the message, or the caption of the media. Present only when there is
text or a caption.
string
ID of the message this one replies to. Present only when the message is a reply.
MESSAGE.SENT fires when WhatsApp accepts a message from your number. It carries the
same messageId the send endpoint returned; messages typed on the phone or another
linked device arrive here too.
Editing or deleting a message does not generate an event.
Message types
object
Present in
IMAGE, VIDEO, AUDIO, DOCUMENT and STICKER. Describes the file;
the file itself is not included in the webhook. Each subfield is present only when
it is known.object
Present in
LOCATION.object
Present in
CONTACT.object
Present in
REACTION.object
Present in
POLL.object
Present in
BUTTON_REPLY.MESSAGE.SENT of a send made through the API:
MESSAGE.RECEIVED with messageType
BUTTON_REPLY arrives, and buttonReply.id is the ID you defined for that option.
MESSAGE.STATUS_UPDATED
What happened to a message you sent: delivered, read or not sent. The “sent” step itself is theMESSAGE.SENT event.
string[]
The messages this status refers to. It is a list because a single receipt can cover
several messages — a contact opening the conversation reads all of them at once, and
a single
READ can carry many IDs.string
DELIVERED (✓✓, arrived on the device) · READ (blue ✓✓) · FAILED (not sent).
The order is DELIVERED < READ: ignore a status older than the one you already
have. FAILED is final: that message did not reach WhatsApp and won’t.string
Why it was not sent, as a stable code — use it to decide and to group by. Present
only when
status is FAILED.NOT_ON_WHATSAPP— the number has no WhatsApp. Resending won’t help.MEDIA_DOWNLOAD_FAILED— couldn’t download the media from the URL (down, 404). Fix the URL and resend.MEDIA_INVALID— the file isn’t valid for this message type. Send another file.MEDIA_UPLOAD_FAILED— WhatsApp rejected the media upload. Resend.INVALID_MESSAGE— WhatsApp doesn’t accept that content there (e.g. a document to a channel, more than 3 reply buttons). Adjust the message.INSTANCE_LOGGED_OUT— the instance was logged out with the message queued. Reconnect and resend.INSTANCE_DELETED— the instance was deleted with the message queued.WHATSAPP_ERROR— WhatsApp refused the send, even after the retries. Seemessage.
string
What happened, in a sentence with the original error — to show or log. Present only
when
status is FAILED.- Send failures: the send answered
202with themessageId, but the message didn’t go out.FAILEDcarries that samemessageId, and there is noMESSAGE.SENTfor it. A sent message never getsFAILED. If the instance only disconnected, queued messages wait and go out when it reconnects — they don’t becomeFAILED. - The status only carries the message IDs. The conversation and the recipient are
the ones in the
MESSAGE.SENTof the same message: link the two bymessageIds. - Groups: the status means the message reached it for at least one participant.
DELIVEREDandREADeach arrive once per message, not once per participant. - Read receipts:
READonly arrives if read receipts are enabled on both numbers — yours and the contact’s. - Only the contact’s receipts. When your own number reads a message it received (opening the conversation on the phone), no status webhook is sent.
CONNECTION.UPDATED
string
Present in
CONNECTED: the phone of the connected number.string
In
CONNECTED: the profile name of the connected number. Absent right after the
first pairing, while WhatsApp has not sent the name yet.string
Present in
BANNED:
SENT_TO_TOO_MANY_PEOPLE (too many messages to people who don’t have your number
saved) · BLOCKED_BY_USERS (too many people blocked you) · CREATED_TOO_MANY_GROUPS
(too many groups with people who don’t have your number) ·
SENT_TOO_MANY_SAME_MESSAGE (the same message to too many people) · BROADCAST_LIST
(too many messages to a broadcast list) · UNKNOWN.number
In
BANNED: seconds until the ban ends. Absent when WhatsApp doesn’t say.QRCODE.UPDATED
The pairing flow, for when you build your own pairing screen.
Pairing completes with
CONNECTION.UPDATED and status: CONNECTED — that is the
signal to close your pairing screen.
string
The QR Code as a data URL (base64 PNG), ready to use in an
<img>.string
The QR Code content, for when you draw the QR yourself.
number
Which QR Code this is in the current pairing attempt, starting at 1.
number
How many QR Codes are generated before
EXPIRED. Absent means no limit.