🔄 Webhooks
On this page
In order to notify your application about events in the WhatsApp API, you can use Webhooks and Websockets.
👉 See the list of all available events in the Events section.
🌟 You can observe Events in real-time using 📊 Dashboard - Event Monitor!
Webhooks
Webhooks are a way for two different applications to communicate with each other in real-time. When a certain event happens in one application, it sends a message to another application through a webhook URL. The receiving application can then take action based on the information received.
Session webhooks
You can define webhooks configuration per session when you start it with POST /api/sessions/
request data.
Here’s a simple example:
{
"name": "default",
"config": {
"webhooks": [
{
"url": "https://webhook.site/11111111-1111-1111-1111-11111111",
"events": [
"message"
]
}
]
}
}
Here’s available configuration options for webhooks
{
"name": "default",
"config": {
"webhooks": [
{
"url": "https://webhook.site/11111111-1111-1111-1111-11111111",
"events": [
"message"
],
"hmac": {
"key": "your-secret-key"
},
"retries": {
"delaySeconds": 2,
"attempts": 15
},
"customHeaders": [
{
"name": "X-My-Custom-Header",
"value": "Value"
}
]
}
]
}
}
Global webhooks
There’s a way how you can configure webhooks for ALL sessions - by settings these environment variables:
WHATSAPP_HOOK_URL=https://webhook.site/11111111-1111-1111-1111-11111111
- to set up a URL for the webhookWHATSAPP_HOOK_EVENTS=message,message.any,state.change
- specify events. Do not specify all of them, it’s too heavy payload, choose the right for you.WHATSAPP_HOOK_EVENTS=*
- subscribe to all events. It’s not recommended for production, but it’s fine for development.
That webhook configuration does not appear in session.config
field in GET /api/sessions/
request.
💡 You can open https://webhook.site and paste URL from it to url
field,
and you’ll see all requests immediately in your browser to intercept the webhook’s payload.
Retries
You can configure retry policy for webhooks by settings config.retries
structure when POST /api/sessions/
:
{
"name": "default",
"config": {
"webhooks": [
{
"url": "https://webhook.site/11111111-1111-1111-1111-11111111",
"events": [
"message"
],
"retries": {
"delaySeconds": 2,
"attempts": 15,
"policy": "constant"
}
}
]
}
}
Possible policy
:
constant
- retry with the same delay between attempts (2, 2, 2, 2)linear
- retry with linear backoff (2, 4, 6, 8)exponential
- retry with exponential backoff with 20% jitter (2, 4.1, 8.4, 16.3).
Headers
When you receive a webhook request to your API endpoint, you’ll get those headers:
X-Webhook-Request-Id
- unique request id for each webhook request.X-Webhook-Timestamp
- Unix timestamp in milliseconds when the webhook was sent.
If you’re using HMAC authentication you’ll get two additional headers:
X-Webhook-Hmac
- message authentication code for the raw body in HTTP POST request that send to your endpoint.X-Webhook-Hmac-Algorithm
-sha512
- algorithm that have been used to createX-Webhook-Hmac
value.
You can send any customer headers by defining config.webhooks.customHeaders
fields this way:
{
"name": "default",
"config": {
"webhooks": [
{
"url": "https://webhook.site/11111111-1111-1111-1111-11111111",
"events": [
"message"
],
"customHeaders": [
{
"name": "X-My-Custom-Header",
"value": "Value"
}
]
}
]
}
}
HMAC authentication
You can authenticate webhook sender by using HMAC Authentication.
- Define you secret key in
config.hmac.key
field when you start session withPOST /api/sessions/
:
{
"name": "default",
"config": {
"webhooks": [
{
"url": "https://webhook.site/11111111-1111-1111-1111-11111111",
"events": [
"message"
],
"hmac": {
"key": "your-secret-key"
}
}
]
}
}
- After that you’ll receive all webhooks payload with two additional headers:
X-Webhook-Hmac
- message authentication code for the raw body in HTTP POST request that send to your endpoint.X-Webhook-Hmac-Algorithm
-sha512
- algorithm that have been used to createX-Webhook-Hmac
value.
- Implement the authentication algorithm by hashing body and using secret key and then verifying it with
X-Webhook-Hmac
value. Please check your implementation here ->
Here’s example for
# Full body
{"event":"message","session":"default","engine":"WEBJS"}
# Secret key
my-secret-key
# X-Webhook-Hmac-Algorithm
sha512
# X-Webhook-Hmac
208f8a55dde9e05519e898b10b89bf0d0b3b0fdf11fdbf09b6b90476301b98d8097c462b2b17a6ce93b6b47a136cf2e78a33a63f6752c2c1631777076153fa89
Examples
Here’s few examples of how to handle webhook in different languages:
Do you use another language?
Please create a short guide how to handle webhook and send message after you finish your setup! You can create a pull request with your favorite language in the GitHub, in examples folder ->.
Websockets
You can use Websockets to receive messages in real-time!
Install websocat first.
# Listen all sessions and events
# -E to end the connection when the server closes it
websocat -E ws://localhost:3000/ws
# Use secure (SSL/HTTPS) connection - add wss://
websocat -E wss://localhost:3000/ws
# Add your API key
websocat -E ws://localhost:3000/ws?x-api-key=123
# Listen all sessions and events
websocat -E ws://localhost:3000/ws?session=*&events=*
# Listen certain events
websocat -E ws://localhost:3000/ws?session=*&events=session.status&events=message
# If you want to see the logs and ping the server every 10 seconds
websocat -v --ping-interval=10 -E ws://localhost:3000/ws
# Listen certain session
websocat -E ws://localhost:3000/ws?session=default&events=session.status
Parameters:
session
- session name,*
for all sessionsevents
- comma-separated list of events,*
for all eventsx-api-key
- your API key
Examples
JavaScript
// Configuration
const apiKey = '123'; // Replace with your API key
const baseUrl = 'ws://localhost:3000/ws';
const session = '*'; // Use '*' to listen to all sessions
const events = ['session.status', 'message']; // List of events to listen to
// Construct the WebSocket URL with query parameters
const queryParams = new URLSearchParams({
'x-api-key': apiKey,
session,
...events.reduce((acc, event) => ({ ...acc, events: event }), {}) // Add multiple 'events' params
});
const wsUrl = `${baseUrl}?${queryParams.toString()}`;
// Initialize WebSocket connection
const socket = new WebSocket(wsUrl);
// Handle incoming messages
socket.onmessage = (event) => {
console.log('Received:', event.data);
};
// Handle errors
socket.onerror = (error) => {
console.error('WebSocket Error:', error);
};
// Handle connection open
socket.onopen = () => {
console.log('WebSocket connection established:', wsUrl);
};
// Handle connection close
socket.onclose = () => {
console.log('WebSocket connection closed');
};
Event Payload
Structure
In Webhooks or Websockets you’ll receive the following payload:
{
"id": "evt_1111111111111111111111111111",
"event": "message",
"session": "default",
// 'metadata' provided when you created the session
"metadata": {
"user.id": "123",
"user.email": "email@example.com"
},
// me - your own contact, if authenticated and WORKING
"me": {
"id": "71111111111@c.us",
"pushName": "~"
},
"payload": {
... // event specific data
},
"environment": {
"tier": "PLUS",
"version": "2023.10.12"
},
"engine": "WEBJS"
}
Metadata
You can provide additional metadata
when you start the session with
Start Session
request data.
{
"event": "message",
"session": "default",
// 'metadata' provided when you created the session
"metadata": {
"user.id": "123",
"user.email": "email@example.com"
},
...
}
You’ll receive the same metadata
in the webhook payload.
Events
Here’s the list of features that are available by 🏭 Engines:
WEBJS | NOWEB | |
---|---|---|
message | ✔️ | ✔️ |
message.reaction | ✔️ | ✔️ |
message.any | ✔️ | ✔️ |
message.ack | ✔️ | ✔️ |
message.waiting | ✔️ | |
message.revoked | ✔️ | |
state.change | ✔️ | ✔️ |
group.join | ✔️ | ✔️ |
group.leave | ✔️ | |
presence.update | ✔️ | |
poll.vote | ✔️ | |
poll.vote.failed | ✔️ | |
chat.archive | ✔️ | |
call.received | ✔️ | ✔️ |
call.accepted | ✔️ | |
call.rejected | ✔️ | |
label.upsert | ✔️ | |
label.deleted | ✔️ | |
label.chat.added | ✔️ | |
label.chat.deleted | ✔️ | |
engine.event | ✔️ | ✔️ |
session.status
The session.status
event is triggered when the session status changes.
STOPPED
- session is stoppedSTARTING
- session is startingSCAN_QR_CODE
- session is required to scan QR code or login via phone number- When you receive the
session.status
event withSCAN_QR_CODE
status, you can fetch updated QR -> - The
SCAN_QR_CODE
is issued every time when QR updated (WhatsApp requirements)
- When you receive the
WORKING
- session is working and ready to useFAILED
- session is failed due to some error. It’s likely that authorization is required again or device has been disconnected from that account. Try to restart the session and if it doesn’t help - logout and start the session again.
{
"event": "session.status",
"session": "default",
"me": {
"id": "7911111@c.us",
"pushName": "~"
},
"payload": {
"status": "WORKING"
},
"engine": "WEBJS",
"environment": {
"version": "2023.10.12",
"engine": "WEBJS",
"tier": "PLUS"
}
}
message
Incoming message (text/audio/files)
{
"event": "message",
"session": "default",
"engine": "WEBJS",
"payload": {
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 1667561485,
"from": "11111111111@c.us",
"fromMe": true,
"to": "11111111111@c.us",
"body": "Hi there!",
"hasMedia": false,
"ack": 1,
"vCards": [],
"_data": {
"id": {
"fromMe": true,
"remote": "11111111111@c.us",
"id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"_serialized": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
},
"body": "Hi there!",
"type": "chat",
"t": 1667561485,
"notifyName": "MyName",
"from": "11111111111@c.us",
"to": "11111111111@c.us",
"self": "in",
"ack": 1,
"isNewMsg": true,
"star": false,
"kicNotified": false,
"recvFresh": true,
"isFromTemplate": false,
"pollInvalidated": false,
"latestEditMsgKey": null,
"latestEditSenderTimestampMs": null,
"broadcast": false,
"mentionedJidList": [],
"isVcardOverMmsDocument": false,
"isForwarded": false,
"hasReaction": false,
"ephemeralOutOfSync": false,
"productHeaderImageRejected": false,
"lastPlaybackProgress": 0,
"isDynamicReplyButtonsMsg": false,
"isMdHistoryMsg": false,
"stickerSentTs": 0,
"isAvatar": false,
"requiresDirectConnection": false,
"pttForwardedFeaturesEnabled": true,
"isEphemeral": false,
"isStatusV3": false,
"links": []
}
}
}
message.any
Fired on all message creations, including your own. The payload is the same as for message event.
{
"event": "message.any",
"session": "default",
"engine": "WEBJS",
"payload": {
...
}
}
message.reaction
Receive events when a message is reacted to by a user (or yourself reacting to a message).
payload.reaction.text
- emoji that was used to react to the message. It’ll be an empty string if the reaction was removed.payload.reaction.messageId
- id of the message that was reacted to.
{
"event": "message.reaction",
"session": "default",
"me": {
"id": "79222222222@c.us",
"pushName": "WAHA"
},
"payload": {
"id": "false_79111111@c.us_11111111111111111111111111111111",
"from": "79111111@c.us",
"fromMe": false,
"participant": "79111111@c.us",
"to": "79111111@c.us",
"timestamp": 1710481111.853,
"reaction": {
"text": "🙏",
"messageId": "true_79111111@c.us_11111111111111111111111111111111"
}
},
"engine": "WEBJS",
"environment": {
"version": "2024.3.3",
"engine": "WEBJS",
"tier": "PLUS",
"browser": "/usr/bin/google-chrome-stable"
}
}
message.ack
Receive events when server or recipient gets the message, read or played it.
ackName
field contains message status (ack
has the same meaning, but show the value in int, but we keep it for backward compatability, they much to each other)
Possible message ack statuses:
ackName: ERROR, ack: -1
ackName: PENDING, ack: 0
ackName: SERVER, ack: 1
ackName: DEVICE, ack: 2
ackName: READ, ack: 3
ackName: PLAYED, ack: 4
The payload may have more fields, it depends on the engine you use, but here’s a minimum amount that all engines send:
{
"event": "message.ack",
"session": "default",
"engine": "WEBJS",
"payload": {
"id":"true_11111111111@c.us_4CC5EDD64BC22EBA6D639F2AF571346C",
"from":"11111111111@c.us",
"participant": null,
"fromMe":true,
"ack":3,
"ackName":"READ"
}
}
message.waiting
Happens when you see Waiting for this message. This may take a while. on your phone.
{
"event": "message.waiting",
"session": "default",
"engine": "WEBJS",
"payload": {
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"timestamp": 1667561485,
"from": "11111111111@c.us",
"fromMe": true,
"to": "11111111111@c.us",
...
"_data": {
...
}
}
}
message.revoked
The message.revoked
event is triggered when a user, whether it be you or any other participant,
revokes a previously sent message.
{
"event": "message.revoked",
"session": "default",
"payload": {
"before": {
"id": "some-id-here",
"timestamp": "some-timestamp-here",
"body": "Hi there!"
},
"after": {
"id": "some-id-here",
"timestamp": "some-timestamp-here",
"body": ""
}
}
}
Important notes:
- The above messages’ ids don’t match any of the ids you’ll receive in the
message
event, it’s a different id. - In order to find the message that was revoked, you’ll need to search for the message with
the same timestamp and chat id as the one in the
after
object. before
field can be null in some cases.
chat.archive
The chat.archive
event is triggered when a chat is archived or unarchived.
{
"event": "chat.archive",
"session": "default",
"payload": {
"id": "123123123@c.us",
"timestamp": 1667561485,
"archived": true <== or false
},
...
}
group.join
{
"event": "group.join",
"session": "default",
"engine": "WEBJS",
"payload": {
...
}
}
group.leave
{
"event": "group.leave",
"session": "default",
"engine": "WEBJS",
"payload": {
...
}
}
presence.update
payload.id
indicates the chat - either direct chat with a contact or a group chat.payload.id.[].participant
- certain participant presence status. For a direct chat there’s only one participant.
{
"event": "presence.update",
"session": "default",
"engine": "NOWEB",
"payload": {
"id": "111111111111111111@g.us",
"presences": [
{
"participant": "11111111111@c.us",
"lastKnownPresence": "typing",
"lastSeen": null
}
]
}
}
poll.vote
We have a dedicated page how to send polls and receive votes!
{
"event": "poll.vote",
"session": "default",
"payload": {
"vote": {
"id": "false_1111111111@c.us_83ACBE602A05C79B234B54415E95EE8A",
"to": "me",
"from": "1111111@c.us",
"fromMe": false,
"selectedOptions": ["Awesome!"],
"timestamp": 1692861427
},
"poll": {
"id": "true_1111111111@c.us_BAE5F2EF5C69001E",
"to": "1111111111@c.us",
"from": "me",
"fromMe": true
}
},
"engine": "NOWEB"
}
poll.vote.failed
We have a dedicated page how to send polls and receive votes!
{
"event": "poll.vote.failed",
"session": "default",
"payload": {
"vote": {
"id": "false_11111111111@c.us_2E8C4CDA89EDE3BC0BC7F605364B8451",
"to": "me",
"from": "111111111@c.us",
"fromMe": false,
"selectedOptions": [],
"timestamp": 1692956972
},
"poll": {
"id": "true_1111111111@c.us_BAE595F4E0A2042C",
"to": "111111111@c.us",
"from": "me",
"fromMe": true
}
},
"engine": "NOWEB"
}
label.upsert
{
"event": "label.upsert",
"session": "default",
"payload": {
"id": "10",
"name": "Label Name",
"color": 14,
"colorHex": "#00a0f2"
},
"engine": "NOWEB",
...
}
label.deleted
{
"event": "label.deleted",
"session": "default",
"payload": {
"id": "10",
"name": "",
"color": 14,
"colorHex": "#00a0f2"
},
"engine": "NOWEB",
...
}
label.chat.added
{
"event": "label.chat.added",
"session": "default",
"payload": {
"labelId": "6",
"chatId": "11111111111@c.us",
"label": null <=== right after scanning QR it can be null.
},
"engine": "NOWEB",
...
}
label.chat.deleted
{
"event": "label.chat.deleted",
"session": "default",
"payload": {
"labelId": "6",
"chatId": "11111111111@c.us",
"label": null
},
"engine": "NOWEB",
...
}
call.received
{
"event": "call.received",
"session": "default",
"payload": {
"id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"from": "22222222222@c.us",
"timestamp": 1721374000,
"isVideo": false,
"isGroup": false
},
...
}
call.accepted
{
"event": "call.accepted",
"session": "default",
"payload": {
"id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"from": "22222222222@c.us",
"timestamp": 1721374000,
"isVideo": false,
"isGroup": false
},
...
}
call.rejected
{
"event": "call.rejected",
"session": "default",
"payload": {
"id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"from": "22222222222@c.us",
"timestamp": 1721374000,
"isVideo": false,
"isGroup": false
},
...
}
engine.event
Low-level engine event, for debug and troubleshooting purposes.
{
"event": "engine.event",
"session": "default",
"engine": "NOWEB",
"payload": {
"event": "messages.upsert",
"data": {"": ""}
}
}
state.change
⚠️ DEPRECATED, use session.status
event instead.
It’s an internal engine’s state, not session status
.
{
"event": "state.change",
"session": "default",
"engine": "WEBJS",
"payload": {
...
}
}