Webhooks
Receive real-time events from WhatsApp via HTTP webhooks.
Register Webhook
Register a webhook for a session.
POST /api/v1/sessions/{session_id}/webhooks
Request Body
{
"url": "https://example.com/webhook",
"events": ["message", "connected", "disconnected"],
"secret": "your-webhook-secret"
}
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Webhook endpoint URL |
events | array | No | Events to subscribe (default: all) |
secret | string | No | HMAC-SHA256 secret for signature |
Response
{
"id": "webhook-uuid",
"url": "https://example.com/webhook",
"events": ["message", "connected", "disconnected"],
"enabled": true
}
List Webhooks
Get all webhooks for a session.
GET /api/v1/sessions/{session_id}/webhooks
Response
{
"webhooks": [
{
"url": "https://example.com/webhook",
"events": ["all"],
"secret": null,
"enabled": true
}
],
"count": 1
}
Delete Webhook
Remove a webhook.
DELETE /api/v1/sessions/{session_id}/webhooks/{webhook_id}
Event Types
Core Events
| Event | Description |
|---|---|
all | Subscribe to all events |
message | New message received |
receipt | Message receipt (delivered, read) |
presence | Contact online/offline status |
chat_presence | Typing indicator |
connected | Connected to WhatsApp |
disconnected | Disconnected from WhatsApp |
logged_out | Logged out from WhatsApp |
qr_code | QR code generated |
pair_code | Pair code generated |
Group Events
| Event | Description |
|---|---|
group_update | Group info changed |
joined_group | Joined a new group |
Contact & Profile Events
| Event | Description |
|---|---|
picture_update | Profile picture changed |
user_about_update | User about/status text changed |
push_name_update | Display name changed |
contact_update | Contact information updated |
device_list_update | Linked devices changed |
Chat Events
| Event | Description |
|---|---|
pin_update | Message pinned or unpinned |
mute_update | Chat muted or unmuted |
archive_update | Chat archived or unarchived |
mark_chat_as_read | Chat marked as read |
System Events
| Event | Description |
|---|---|
undecryptable_message | Received a message that could not be decrypted |
client_outdated | Client version is outdated |
offline_sync_preview | Preview of offline messages available |
offline_sync_completed | Offline message sync completed |
Webhook Payload
All webhook payloads follow this format:
{
"session_id": "my-session",
"event": "message",
"timestamp": 1767143203,
"data": {
// Event-specific data
}
}
Message Event
{
"session_id": "my-session",
"event": "message",
"timestamp": 1767143203,
"data": {
"from": "628123456789@s.whatsapp.net",
"chat": "628123456789@s.whatsapp.net",
"message_id": "3EB0ABC123...",
"is_from_me": false
}
}
Connected Event
{
"session_id": "my-session",
"event": "connected",
"timestamp": 1767143203,
"data": {}
}
Picture Update Event
{
"session_id": "my-session",
"event": "picture_update",
"timestamp": 1767143203,
"data": {
"jid": "628123456789@s.whatsapp.net",
"action": "set"
}
}
Pin Update Event
{
"session_id": "my-session",
"event": "pin_update",
"timestamp": 1767143203,
"data": {
"chat_jid": "628123456789@s.whatsapp.net",
"pinned": true
}
}
Signature Verification
If you provide a secret, WA-RS will sign the payload with HMAC-SHA256.
The signature is sent in the X-Webhook-Signature header:
X-Webhook-Signature: sha256=abc123...
Verification Example (Node.js)
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express middleware
app.post('/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = JSON.stringify(req.body);
if (!verifySignature(payload, signature, 'your-secret')) {
return res.status(401).send('Invalid signature');
}
// Process webhook
console.log(req.body);
res.sendStatus(200);
});
Verification Example (Python)
import hmac
import hashlib
def verify_signature(payload, signature, secret):
expected = 'sha256=' + hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
Best Practices
- Always verify signatures in production
- Respond quickly (within 5 seconds) to avoid timeouts
- Use HTTPS for webhook endpoints
- Handle duplicates - webhooks may be retried
- Log everything for debugging