Webhooks
Receive real-time WhatsApp events — inbound messages, receipts, connection state, calls, groups and more — pushed to your server.
Webhooks let your application receive WhatsApp events the moment they happen,
instead of polling the API. When a configured event occurs on a device, WUTS
sends an HTTP POST to the URL you registered, carrying a JSON payload that
describes exactly what happened. Common uses:
- Inbound messages — react to texts, media, locations, buttons and list replies.
- Delivery receipts — track when outbound messages are delivered, read or played.
- Connection / session state — know when a device connects, disconnects, logs out or is paired.
- Calls — be notified of incoming call offers, accepts, rejections and terminations.
- Groups & contacts — group info changes, joins, presence and profile updates.
All webhook endpoints require the standard bearer token — see Authentication. Configuration is scoped to the device that owns the token.
Configure a webhook
Send a POST /webhook with the target URL and the events you want delivered.
Calling it again updates the existing configuration for the device.
curl -X POST https://api.wuts.com.br/webhook \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-app.com/wuts/webhook",
"enabled_events": [
"message.received",
"message.delivered",
"message.read",
"device.connected",
"device.disconnected"
],
"enabled": true,
"retry_attempts": 3,
"timeout": 10,
"secret": "a-long-random-string"
}'| Field | Type | Required | Description |
|---|---|---|---|
webhook_url | string | yes | HTTPS URL that will receive the POST callbacks. |
enabled_events | string[] | yes | One or more event names (see the catalog). At least one is required. |
enabled | boolean | no | Whether delivery is active. Defaults to true. |
retry_attempts | integer | no | Retries on failure. Defaults to 3. |
timeout | integer | no | Per-request timeout in seconds. Defaults to 10. |
secret | string | no | Key used to sign each payload with HMAC-SHA256. |
headers | object | no | Custom headers added to every callback request. |
A successful response returns the stored configuration, where webhook echoes
back id, webhook_url, enabled_events, enabled, retry_attempts,
timeout, headers, created_at, and updated_at:
{ "success": true, "message": "Webhook configured successfully", "webhook": { "...": "..." } }Passing an event name that is not in the catalog returns 400 with an
available_events array listing every valid name. Validate against
GET /webhook/events before configuring.
Read, list and remove
# Current configuration for the device (404 if none is set)
curl https://api.wuts.com.br/webhook \
-H "Authorization: Bearer <token>"
# Every event name you can subscribe to
curl https://api.wuts.com.br/webhook/events \
-H "Authorization: Bearer <token>"
# Stop and delete the configuration
curl -X DELETE https://api.wuts.com.br/webhook \
-H "Authorization: Bearer <token>"GET /webhook returns the stored configuration (or 404 with
"error": "Webhook not configured" when none is set), and GET /webhook/events
returns the full list of subscribable event names:
{ "success": true, "webhook": { "...": "..." } }
{ "success": true, "events": ["message", "message.ack", "..."] }Delivery model
Callbacks are pushed in real time as a POST with a JSON body. WUTS treats any
2xx status as success; any other status, a connection error, or exceeding the
configured timeout triggers a retry.
- Retries follow
retry_attemptswith an incremental backoff (e.g. 1s, 2s, 3s). - Custom
headers(if configured) are sent on every attempt. - Only events listed in
enabled_eventsare delivered; everything else is dropped.
Respond quickly with 200 and process asynchronously. Slow handlers count
against timeout and cause unnecessary retries — and your endpoint should be
idempotent, since a retried delivery may arrive more than once.
Payload envelope
Every callback shares the same outer shape. The data object varies by event.
{
"id": "evt-3f2b9c1a",
"event": "message.received",
"timestamp": 1781827200,
"device_id": "device-uuid",
"data": { },
"signature": "sha256=..."
}| Field | Type | Description |
|---|---|---|
id | string | Unique id of this event delivery. |
event | string | Event name from the catalog. |
timestamp | integer | Unix epoch seconds when the event was emitted. |
device_id | string | Device that produced the event. |
data | object | Event-specific payload (examples below). |
signature | string | Present only when a secret is configured (see Verifying). |
Example: inbound message
{
"id": "evt-3f2b9c1a",
"event": "message.received",
"timestamp": 1781827200,
"device_id": "device-uuid",
"data": {
"message_id": "3EB0F8A1B2C3D4E5F6",
"from": "5511999999999@s.whatsapp.net",
"to": "5511888888888@s.whatsapp.net",
"content": "Hi there!",
"message_type": "text",
"is_from_me": false,
"push_name": "Maria",
"timestamp": "2026-06-15T12:00:00Z"
}
}Media messages include a media object (with mime_type, caption and a stable
media_path you can fetch with auth); documents, locations, contacts, polls and
interactive button/list replies populate their own sub-objects. See
Working with media.
Example: delivery receipt
{
"id": "evt-7a1d4e8b",
"event": "message.delivered",
"timestamp": 1781827205,
"device_id": "device-uuid",
"data": {
"message_ids": ["3EB0F8A1B2C3D4E5F6"],
"chat_jid": "5511999999999@s.whatsapp.net",
"receipt_type": "delivery",
"message_count": 1,
"is_group": false,
"delivered_at": "2026-06-15T12:00:05Z"
}
}Example: connection event
{
"id": "evt-9c0f2a31",
"event": "device.connected",
"timestamp": 1781827100,
"device_id": "device-uuid",
"data": {
"device_id": "device-uuid",
"status": "connected",
"whatsapp_jid": "5511999999999@s.whatsapp.net",
"is_connected": true,
"is_logged_in": true
}
}Event catalog
Every subscribable event, grouped by category. The authoritative list is always
GET /webhook/events.
| Category | Events |
|---|---|
| Messages | message.received, message.sent, message.delivered, message.read, message.undecryptable, message.delete_for_me, mention.sent, media.retry |
| Connection & session | device.connected, device.disconnected, device.logged_out, connection.pair_error, connection.stream_replaced, connection.failure, connection.temporary_ban, connection.manual_reconnect, connection.keepalive_timeout, connection.keepalive_restored, qr.generated, pair.success |
| Contacts & chats | contact.presence, contact.changed, contact.pushname, contact.about, contact.picture, chat.presence, chat.mark_as_read, chat.pin, chat.mute, chat.archive, chat.delete, chat.clear |
| Groups | group.info, group.joined (see Group management) |
| Calls | call.offer, call.accept, call.pre_accept, call.reject, call.terminate, call.relay_latency, call.transport, call.unknown |
| Security & privacy | identity.change, privacy.blocklist, privacy.settings |
| Settings & labels | settings.unarchive_chats, settings.user_status_mute, label.edit, label.association_chat, label.association_message |
| Newsletters | newsletter.join, newsletter.leave, newsletter.live_update, newsletter.mute_change |
| Sync | history.sync, offline.sync_preview, offline.sync_completed, app_state.sync_complete |
Verifying deliveries
When you set a secret, every callback includes a signature field. It is an
HMAC-SHA256 of the JSON body keyed by your secret, prefixed with sha256=.
Recompute it and compare in constant time:
const crypto = require("crypto");
function verify(payload, signature, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(JSON.stringify(payload)).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}If you do not configure a secret, the signature field is absent. In that
case, protect your endpoint another way: serve it on an unguessable secret path,
require a custom value via the headers option, and/or allowlist WUTS source
IPs. Always use HTTPS so payloads are encrypted in transit.
Next steps
- Receive messages — handle inbound events end to end.
- Connect a device — pair before events start flowing.
- Errors and Rate limits — handle failures gracefully.
- Conventions — shared JID, timestamp and pagination formats.
Group Management
Create, inspect, and administer WhatsApp groups and communities — participants, settings, invite links, and disappearing messages.
Reject an incoming call POST
Attempts a best-effort rejection of an incoming WhatsApp call for the authenticated WUTS device. Because WhatsApp does not reliably honour call rejection from a linked/companion device, the user-facing behaviour is typically the configured auto-reply message; the endpoint is kept so hang-up starts working automatically if the platform begins honouring it.