# Webhook Payload Documentation

This document provides comprehensive documentation for the webhook payload structure used by the Go WhatsApp Web
Multidevice application.

## Overview

The webhook system sends HTTP POST requests to configured URLs whenever WhatsApp events occur. Each webhook request
includes event data in JSON format and security headers for verification.

## Available Webhook Events

The following events can be received via webhook:

| Event                | Description                                             |
|----------------------|---------------------------------------------------------|
| `message`            | Text, media, contact, location, and other message types |
| `message.reaction`   | Emoji reactions to messages                             |
| `message.revoked`    | Deleted/revoked messages                                |
| `message.edited`     | Edited messages                                         |
| `message.ack`        | Delivery and read receipts                              |
| `message.deleted`    | Messages deleted for the user                           |
| `chat_presence`      | Typing and recording indicators from contacts           |
| `group.participants` | Group member join/leave/promote/demote events           |
| `group.joined`       | You were added to a group                               |
| `label.edit`         | WhatsApp label metadata changed                         |
| `label.association`  | Label applied to or removed from a chat                 |
| `newsletter.joined`  | You subscribed to a newsletter/channel                  |
| `newsletter.left`    | You unsubscribed from a newsletter                      |
| `newsletter.message` | New message(s) posted in a newsletter                   |
| `newsletter.mute`    | Newsletter mute setting changed                         |
| `call.offer`         | Incoming call received                                  |

## Event Filtering

You can configure which events are forwarded to your webhook using the `WHATSAPP_WEBHOOK_EVENTS` environment variable or
`--webhook-events` CLI flag.

### Configuration Examples

**Environment Variable:**

```bash
# Only receive message and read receipt events
WHATSAPP_WEBHOOK_EVENTS=message,message.ack

# Receive all message-related events
WHATSAPP_WEBHOOK_EVENTS=message,message.reaction,message.revoked,message.edited,message.ack,message.deleted

# Receive only group events
WHATSAPP_WEBHOOK_EVENTS=group.participants

# Receive label events
WHATSAPP_WEBHOOK_EVENTS=label.edit,label.association

# Receive newsletter events
WHATSAPP_WEBHOOK_EVENTS=newsletter.joined,newsletter.left,newsletter.message,newsletter.mute

# Receive call events
WHATSAPP_WEBHOOK_EVENTS=call.offer

# Receive all group and newsletter events
WHATSAPP_WEBHOOK_EVENTS=group.participants,group.joined,newsletter.joined,newsletter.left,newsletter.message
```

**CLI Flag:**

```bash
# Only receive message events
./whatsapp rest --webhook="https://yourapp.com/webhook" --webhook-events="message"

# Receive message and group events
./whatsapp rest --webhook="https://yourapp.com/webhook" --webhook-events="message,group.participants"
```

**Behavior:**

- If `WHATSAPP_WEBHOOK_EVENTS` is empty or not set, **all events** are forwarded (default behavior)
- If configured, only the specified events are forwarded to webhooks
- Event names are case-insensitive

### Ignoring chats/JIDs

In addition to filtering by **event type** above, you can skip events by **conversation or sender JID** —
for example, to mute all group traffic from the webhook. This is independent of `WHATSAPP_WEBHOOK_EVENTS`
(the two filters compose: an event is forwarded only if its type is allowed **and** its JID is not ignored).

**Environment Variable:**

```bash
# Drop all group messages/receipts (any chat_id ending in @g.us)
WHATSAPP_WEBHOOK_IGNORE_JIDS=@g.us

# Drop all groups plus one specific 1:1 chat
WHATSAPP_WEBHOOK_IGNORE_JIDS=@g.us,628123456789@s.whatsapp.net
```

**CLI Flag:**

```bash
./whatsapp rest --webhook="https://yourapp.com/webhook" --webhook-ignore-jids="@g.us"
```

**Behavior:**

- Matches the event's `chat_id`, `from`, `chat_lid` or `from_lid` against the list (so an `@lid`
  pattern matches LID-migrated events, whose `@lid` JID lives in the `*_lid` fields).
- An `@`-prefixed entry is an address-space **wildcard** (`@g.us`, `@s.whatsapp.net`, `@lid`); any other
  entry is an **exact** JID match.
- Empty/unset (default) forwards everything.
- Independent from the Chatwoot integration, which keeps its own `CHATWOOT_IGNORE_JIDS`.
- `@g.us` is the recommended way to mute groups (it matches the group `chat_id`). The
  `@s.whatsapp.net` wildcard matches the **sender** too, so it also suppresses group messages (whose
  `from` is the participant's `@s.whatsapp.net` JID) — use exact JIDs if you only want to mute specific
  1:1 chats.
- Note: a few events carry the group JID elsewhere or omit `chat_id` — `call.offer` puts the group in
  `group_jid` (not `chat_id`), `message.deleted` only includes `chat_id` when the original message is
  found locally, and `group.participants`/`group.joined` have no `from`. The `@g.us` wildcard still
  covers ordinary group messages and receipts (which is the common case for muting groups).

## Security

### HMAC Signature Verification

All webhook requests include an HMAC SHA256 signature for security verification:

- **Header**: `X-Hub-Signature-256`
- **Format**: `sha256={signature}`
- **Algorithm**: HMAC SHA256
- **Default Secret**: `secret` (configurable via `--webhook-secret` or `WHATSAPP_WEBHOOK_SECRET`)

### Verification Example (Node.js)

```javascript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
    const expectedSignature = crypto
        .createHmac('sha256', secret)
        .update(payload, 'utf8')
        .digest('hex');

    const receivedSignature = signature.replace('sha256=', '');
    return crypto.timingSafeEqual(
        Buffer.from(expectedSignature, 'hex'),
        Buffer.from(receivedSignature, 'hex')
    );
}
```

### Verification Example (Python)

```python
import hmac
import hashlib

def verify_webhook_signature(payload, signature, secret):
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()

    received_signature = signature.replace('sha256=', '')
    return hmac.compare_digest(expected_signature, received_signature)
```

## Payload Structure

All webhook payloads follow a consistent top-level structure:

```json
{
  "event": "message",
  "device_id": "628123456789@s.whatsapp.net",
  "session_id": "org_2",
  "payload": {
    // Event-specific fields
  }
}
```

### Top-Level Fields

| **Field**    | **Type** | **Description**                                                                                                     |
|--------------|----------|---------------------------------------------------------------------------------------------------------------------|
| `event`      | string   | Event type: `message`, `message.reaction`, `message.revoked`, `message.edited`, `message.ack`, `message.deleted`, `chat_presence`, `group.participants`, `group.joined`, `label.edit`, `label.association`, `newsletter.joined`, `newsletter.left`, `newsletter.message`, `newsletter.mute`, `call.offer` |
| `device_id`  | string   | JID of the device that received this event (e.g., `628123456789@s.whatsapp.net`)                                    |
| `session_id` | string   | Session ID registered via `POST /devices` (e.g., `org_2`), for correlating the event back to a tenant. Omitted when the JID can't be mapped to a session. |
| `payload`    | object   | Event-specific payload data                                                                                         |

### Common Payload Fields

Fields commonly found inside the `payload` object:

| **Field**   | **Type** | **Description**                                                               |
|-------------|----------|-------------------------------------------------------------------------------|
| `id`        | string   | Message ID                                                                    |
| `chat_id`   | string   | Chat JID (e.g., `628987654321@s.whatsapp.net` or `120363...@g.us` for groups) |
| `from`      | string   | Full JID of the sender (e.g., `628123456789@s.whatsapp.net`)                  |
| `from_lid`  | string   | LID (Linked ID) of the sender if available                                    |
| `from_name` | string   | Display name (pushname) of the sender                                         |
| `timestamp` | string   | RFC3339 formatted timestamp (e.g., `2023-10-15T10:30:00Z`)                    |
| `is_from_me` | boolean | Whether the message was sent by the current user                              |

## Message Events

### Text Message

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6A1",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_lid": "251556368777322@lid",
    "from_name": "John Doe",
    "timestamp": "2023-10-15T10:30:00Z",
    "is_from_me": false,
    "body": "Hello, how are you?"
  }
}
```

### Reply Message

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6A2",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2023-10-15T10:35:00Z",
    "is_from_me": false,
    "body": "I'm doing great, thanks!",
    "replied_to_id": "3EB0C127D7BACC83D6A1",
    "quoted_body": "Hello, how are you?"
  }
}
```

### Reaction Message

```json
{
  "event": "message.reaction",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "88760C69D1F35FEB239102699AE9XXXX",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2023-10-15T10:40:00Z",
    "is_from_me": false,
    "reaction": "👍",
    "reacted_message_id": "3EB0C127D7BACC83D6A1"
  }
}
```

## Receipt Events

Receipt events are triggered when messages receive acknowledgments such as delivery confirmations and read receipts.
These events use the `message.ack` event type and provide information about message status changes.

### Message Delivered

Triggered when a message is successfully delivered to the recipient's device.

```json
{
  "event": "message.ack",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2025-07-18T22:44:20Z",
  "payload": {
    "ids": [
      "3EB00106E8BE0F407E88EC"
    ],
    "chat_id": "120363402106XXXXX@g.us",
    "from": "6289685XXXXXX@s.whatsapp.net",
    "from_lid": "251556368777322@lid",
    "receipt_type": "delivered",
    "receipt_type_description": "means the message was delivered to the device (but the user might not have noticed)."
  }
}
```

### Message Read

Triggered when a message is read by the recipient (they opened the chat and saw the message).

```json
{
  "event": "message.ack",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2025-07-18T22:44:44Z",
  "payload": {
    "ids": [
      "3EB00106E8BE0F407E88EC"
    ],
    "chat_id": "120363402106XXXXX@g.us",
    "from": "6289685XXXXXX@s.whatsapp.net",
    "receipt_type": "read",
    "receipt_type_description": "the user opened the chat and saw the message."
  }
}
```

### Receipt Event Fields

| **Field**                          | **Type** | **Description**                                           |
|------------------------------------|----------|-----------------------------------------------------------|
| `event`                            | string   | Always `"message.ack"` for receipt events                 |
| `device_id`                        | string   | JID of the device that received this event                |
| `timestamp`                        | string   | RFC3339 formatted timestamp when the receipt was received |
| `payload.ids`                      | array    | Array of message IDs that received the acknowledgment     |
| `payload.chat_id`                  | string   | Chat identifier (group or individual chat)                |
| `payload.from`                     | string   | JID of the user who triggered the receipt                 |
| `payload.from_lid`                 | string   | LID of the user (if available)                            |
| `payload.receipt_type`             | string   | Type of receipt: `"delivered"`, `"read"`, etc.            |
| `payload.receipt_type_description` | string   | Human-readable description of the receipt type            |

## Chat Presence Events

Chat presence events are triggered when a contact starts or stops typing (or recording audio) in a chat.
These events use the `chat_presence` event type and are useful for implementing message batching strategies.

**Note:** WhatsApp only sends chat presence updates when the client is marked as online. GOWA defaults to
`unavailable` on connection, but the daily presence pulse periodically marks connected devices as `available`
and then returns them to `unavailable`.

### User Typing

Triggered when a user starts typing a text message.

```json
{
  "event": "chat_presence",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2026-01-22T12:00:00Z",
  "payload": {
    "from": "628987654321@s.whatsapp.net",
    "chat_id": "628987654321@s.whatsapp.net",
    "state": "composing",
    "media": "",
    "is_group": false
  }
}
```

### User Stopped Typing

Triggered when a user stops typing (pauses or clears the input field).

```json
{
  "event": "chat_presence",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2026-01-22T12:00:05Z",
  "payload": {
    "from": "628987654321@s.whatsapp.net",
    "chat_id": "628987654321@s.whatsapp.net",
    "state": "paused",
    "media": "",
    "is_group": false
  }
}
```

### User Recording Audio

Triggered when a user starts recording a voice message.

```json
{
  "event": "chat_presence",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2026-01-22T12:01:00Z",
  "payload": {
    "from": "628987654321@s.whatsapp.net",
    "chat_id": "628987654321@s.whatsapp.net",
    "state": "composing",
    "media": "audio",
    "is_group": false
  }
}
```

### Group Typing

Triggered when a user starts typing in a group chat.

```json
{
  "event": "chat_presence",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2026-01-22T12:02:00Z",
  "payload": {
    "from": "628987654321@s.whatsapp.net",
    "from_lid": "251556368777322@lid",
    "chat_id": "120363402106XXXXX@g.us",
    "state": "composing",
    "media": "",
    "is_group": true
  }
}
```

### Chat Presence Event Fields

| **Field**          | **Type** | **Description**                                                    |
|--------------------|----------|--------------------------------------------------------------------|
| `event`            | string   | Always `"chat_presence"` for typing events                         |
| `device_id`        | string   | JID of the device that received this event                         |
| `timestamp`        | string   | RFC3339 formatted timestamp when the event was processed           |
| `payload.from`     | string   | JID of the user who is typing (e.g., `628987654321@s.whatsapp.net`)|
| `payload.from_lid` | string   | LID of the user (if available, typically in group chats)           |
| `payload.chat_id`  | string   | Chat identifier (individual or group)                              |
| `payload.state`    | string   | Typing state: `"composing"` (typing) or `"paused"` (stopped)      |
| `payload.media`    | string   | Media type: `""` (text message) or `"audio"` (voice recording)    |
| `payload.is_group` | boolean  | Whether this is a group chat                                       |

## Label Events

Label events are triggered when WhatsApp label metadata changes or a label is applied to or removed from a chat. These
events come from WhatsApp app-state sync and are forwarded as `label.edit` or `label.association`.

### Label Edit

Triggered when a WhatsApp label is created, renamed, reordered, activated/deactivated, or deleted.

```json
{
  "event": "label.edit",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2026-01-22T12:03:00Z",
  "payload": {
    "label_id": "9",
    "name": "Important",
    "color": 2,
    "predefined_id": "1",
    "deleted": false,
    "order_index": 1,
    "is_active": true,
    "type": "CUSTOM",
    "is_immutable": false,
    "mute_end_time_ms": 0
  }
}
```

### Label Association

Triggered when a chat-level label is applied to or removed from a chat.

```json
{
  "event": "label.association",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2026-01-22T12:04:00Z",
  "payload": {
    "label_id": "9",
    "labeled": true,
    "chat_id": "120363402106XXXXX@g.us"
  }
}
```

### Label Event Fields

| **Field**                | **Type** | **Description**                                                                  |
|--------------------------|----------|----------------------------------------------------------------------------------|
| `event`                  | string   | `"label.edit"` for label metadata changes, or `"label.association"` for chat labels |
| `device_id`              | string   | JID of the device that received this event                                       |
| `timestamp`              | string   | RFC3339 formatted timestamp from the app-state event, or processing time fallback |
| `payload.label_id`       | string   | WhatsApp label identifier                                                        |
| `payload.name`           | string   | Label display name (only when included by WhatsApp on `label.edit`)              |
| `payload.color`          | number   | Label color value (only when included by WhatsApp on `label.edit`)               |
| `payload.predefined_id`  | string   | Predefined label identifier (only when included by WhatsApp on `label.edit`)      |
| `payload.deleted`        | boolean  | Whether the label was deleted (only when included by WhatsApp on `label.edit`)    |
| `payload.order_index`    | number   | Label ordering value (only when included by WhatsApp on `label.edit`)             |
| `payload.is_active`      | boolean  | Whether the label is active (only when included by WhatsApp on `label.edit`)      |
| `payload.type`           | string   | WhatsApp label type (only when included by WhatsApp on `label.edit`)              |
| `payload.is_immutable`   | boolean  | Whether the label is immutable (only when included by WhatsApp on `label.edit`)   |
| `payload.mute_end_time_ms` | number | Label mute end time in milliseconds (only when included by WhatsApp on `label.edit`) |
| `payload.labeled`        | boolean  | Whether the label was applied (`true`) or removed (`false`) on `label.association` |
| `payload.chat_id`        | string   | Chat JID associated with the label on `label.association`                         |
| `payload.chat_lid`       | string   | Original LID chat identifier when WhatsApp supplied a LID before normalization    |

## Group Events

Group events are triggered when group metadata changes, including member join/leave events, admin promotions/demotions,
and group settings updates. These events use the `group.participants` event type and provide comprehensive information
about group changes.

### Group Member Join

Triggered when users join or are added to a group.

```json
{
  "event": "group.participants",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2025-07-28T10:30:00Z",
  "payload": {
    "chat_id": "120363402106XXXXX@g.us",
    "type": "join",
    "jids": [
      "6289685XXXXXX@s.whatsapp.net",
      "6289686YYYYYY@s.whatsapp.net"
    ]
  }
}
```

### Group Member Leave

Triggered when users leave or are removed from a group.

```json
{
  "event": "group.participants",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2025-07-28T10:32:00Z",
  "payload": {
    "chat_id": "120363402106XXXXX@g.us",
    "type": "leave",
    "jids": [
      "6289687ZZZZZZ@s.whatsapp.net"
    ]
  }
}
```

### Group Member Promotion

Triggered when users are promoted to admin.

```json
{
  "event": "group.participants",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2025-07-28T10:33:00Z",
  "payload": {
    "chat_id": "120363402106XXXXX@g.us",
    "type": "promote",
    "jids": [
      "6289688AAAAAA@s.whatsapp.net"
    ]
  }
}
```

### Group Member Demotion

Triggered when users are demoted from admin.

```json
{
  "event": "group.participants",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2025-07-28T10:34:00Z",
  "payload": {
    "chat_id": "120363402106XXXXX@g.us",
    "type": "demote",
    "jids": [
      "6289689BBBBBB@s.whatsapp.net"
    ]
  }
}
```

### Group Event Fields

| **Field**         | **Type** | **Description**                                              |
|-------------------|----------|--------------------------------------------------------------|
| `event`           | string   | Always `"group.participants"` for group events               |
| `device_id`       | string   | JID of the device that received this event                   |
| `timestamp`       | string   | RFC3339 formatted timestamp when the group event occurred    |
| `payload.chat_id` | string   | Group identifier (e.g., `"120363402106XXXXX@g.us"`)          |
| `payload.type`    | string   | Action type: `"join"`, `"leave"`, `"promote"`, or `"demote"` |
| `payload.jids`    | array    | Array of user JIDs affected by this action                   |

## Newsletter Events

Newsletter events are triggered when you interact with WhatsApp Channels (newsletters). These include subscribing,
unsubscribing, receiving new messages, and mute setting changes.

### Newsletter Joined

Triggered when you subscribe to a newsletter/channel.

```json
{
  "event": "newsletter.joined",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2026-01-18T12:00:00Z",
  "payload": {
    "newsletter_id": "120363123456789@newsletter",
    "name": "Tech News Daily",
    "description": "Latest tech updates and news"
  }
}
```

### Newsletter Left

Triggered when you unsubscribe from a newsletter/channel.

```json
{
  "event": "newsletter.left",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2026-01-18T12:00:00Z",
  "payload": {
    "newsletter_id": "120363123456789@newsletter",
    "role": "subscriber"
  }
}
```

### Newsletter Message

Triggered when new messages are posted in a newsletter you're subscribed to.

```json
{
  "event": "newsletter.message",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2026-01-18T12:00:00Z",
  "payload": {
    "newsletter_id": "120363123456789@newsletter",
    "messages": [
      {
        "server_id": 123,
        "message_id": "ABC123DEF456",
        "type": "text",
        "timestamp": "2026-01-18T12:00:00Z",
        "views_count": 1500,
        "reaction_counts": {"👍": 50, "❤️": 25}
      }
    ]
  }
}
```

### Newsletter Mute

Triggered when you mute or unmute a newsletter.

```json
{
  "event": "newsletter.mute",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2026-01-18T12:00:00Z",
  "payload": {
    "newsletter_id": "120363123456789@newsletter",
    "mute": "on"
  }
}
```

### Newsletter Event Fields

| **Field**                      | **Type** | **Description**                                         |
|--------------------------------|----------|---------------------------------------------------------|
| `event`                        | string   | Event type (see table above)                            |
| `device_id`                    | string   | JID of the device that received this event              |
| `timestamp`                    | string   | RFC3339 formatted timestamp                             |
| `payload.newsletter_id`        | string   | Newsletter identifier (e.g., `120363...@newsletter`)    |
| `payload.name`                 | string   | Newsletter name (only in `newsletter.joined`)           |
| `payload.description`          | string   | Newsletter description (only in `newsletter.joined`)    |
| `payload.role`                 | string   | Your role in the newsletter (only in `newsletter.left`) |
| `payload.mute`                 | string   | Mute state: `"on"` or `"off"` (only in `newsletter.mute`)|
| `payload.messages`             | array    | Array of messages (only in `newsletter.message`)        |
| `payload.messages[].server_id` | number   | Server-assigned message ID                              |
| `payload.messages[].message_id`| string   | Message identifier                                      |
| `payload.messages[].type`      | string   | Message type (e.g., `"text"`, `"image"`)                |
| `payload.messages[].timestamp` | string   | Message timestamp                                       |
| `payload.messages[].views_count`| number  | Number of views (if available)                          |
| `payload.messages[].reaction_counts`| object | Reaction emoji counts (if available)                 |

## Call Events

Call events are triggered when you receive an incoming WhatsApp call. You can optionally auto-reject calls using the
`WHATSAPP_AUTO_REJECT_CALL` environment variable or `--auto-reject-call` CLI flag.

When chat storage is enabled, each incoming call is also persisted as a synthetic message in the chat history:
`media_type` is `call`, `content` is `Incoming call`, and `call_metadata` (JSON) holds `call_id`, `auto_rejected`, and
optional `remote_platform`, `remote_version`, and `group_jid`. List chat messages via the REST/MCP chat APIs to retrieve these rows.

### Call Offer

Triggered when an incoming call is received.

```json
{
  "event": "call.offer",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2026-02-05T12:00:00Z",
  "payload": {
    "call_id": "ABC123DEF456",
    "from": "628987654321@s.whatsapp.net",
    "auto_rejected": false,
    "remote_platform": "android",
    "remote_version": "2.24.1.5"
  }
}
```

### Call Offer with Auto-Reject Enabled

When `WHATSAPP_AUTO_REJECT_CALL=true`, calls are automatically rejected and the webhook includes this status:

```json
{
  "event": "call.offer",
  "device_id": "628123456789@s.whatsapp.net",
  "timestamp": "2026-02-05T12:00:00Z",
  "payload": {
    "call_id": "ABC123DEF456",
    "from": "628987654321@s.whatsapp.net",
    "auto_rejected": true,
    "remote_platform": "android",
    "remote_version": "2.24.1.5",
    "group_jid": "120363402106XXXXX@g.us"
  }
}
```

### Call Event Fields

| **Field**                 | **Type** | **Description**                                            |
|---------------------------|----------|------------------------------------------------------------|
| `event`                   | string   | Always `"call.offer"` for call events                      |
| `device_id`               | string   | JID of the device that received this event                 |
| `timestamp`               | string   | RFC3339 formatted timestamp when the call was received     |
| `payload.call_id`         | string   | Unique identifier for the call                             |
| `payload.from`            | string   | JID of the caller                                          |
| `payload.auto_rejected`   | boolean  | Whether the call was auto-rejected                         |
| `payload.remote_platform` | string   | Platform of the caller (e.g., `"android"`, `"ios"`)        |
| `payload.remote_version`  | string   | WhatsApp version of the caller                             |
| `payload.group_jid`       | string   | Group JID if this is a group call (optional)               |

### Configuration

**Environment Variable:**

```bash
# Auto-reject all incoming calls
WHATSAPP_AUTO_REJECT_CALL=true
```

**CLI Flag:**

```bash
# Auto-reject all incoming calls
./whatsapp rest --auto-reject-call=true
```

### Reject Call via API

In addition to auto-rejecting all calls, you can programmatically reject specific calls using the REST API. This is useful when you want to apply custom logic (e.g., time-of-day rules, caller whitelists, or agent availability checks).

**Endpoint:** `POST /call/reject`

**Headers:**
```http
X-Device-Id: <device_id>
Content-Type: application/json
```

**Request Body:**
```json
{
  "caller_jid": "628987654321@s.whatsapp.net",
  "call_id": "ABC123DEF456"
}
```

**Where to get these values:**

When you receive a `call.offer` webhook event, extract the values from the payload:

```json
{
  "event": "call.offer",
  "device_id": "628123456789@s.whatsapp.net",
  "payload": {
    "call_id": "ABC123DEF456",
    "from": "628987654321@s.whatsapp.net",
    "auto_rejected": false
  }
}
```

- `caller_jid` → Use `payload.from` from the webhook
- `call_id` → Use `payload.call_id` from the webhook

**Example (curl):**

```bash
curl -X POST http://localhost:3000/call/reject \
  -H "X-Device-Id: 628123456789@s.whatsapp.net" \
  -H "Content-Type: application/json" \
  -d '{
    "caller_jid": "628987654321@s.whatsapp.net",
    "call_id": "ABC123DEF456"
  }'
```

**Example (JavaScript/Node.js):**

```javascript
const axios = require('axios');

// When you receive a call.offer webhook event
app.post('/webhook', async (req, res) => {
  const { event, payload, device_id } = req.body;

  if (event === 'call.offer' && !payload.auto_rejected) {
    // Apply your custom logic here
    const shouldReject = checkBusinessHours() || isBlacklisted(payload.from);

    if (shouldReject) {
      try {
        await axios.post('http://localhost:3000/call/reject', {
          caller_jid: payload.from,
          call_id: payload.call_id
        }, {
          headers: {
            'X-Device-Id': device_id,
            'Content-Type': 'application/json'
          }
        });
        console.log(`Rejected call from ${payload.from}`);
      } catch (error) {
        console.error('Failed to reject call:', error.message);
      }
    }
  }

  res.sendStatus(200);
});
```

**Important Notes:**

- **Timing:** The call must still be ringing. Rejection will fail if the call has already ended or been answered.
- **Device-scoped:** The `X-Device-Id` header is required and must match the device that received the call.
- **Error handling:** If the call has already ended, the API will return an error. Handle this gracefully in your application.
- **Complementary to auto-reject:** If `WHATSAPP_AUTO_REJECT_CALL=true`, all calls are rejected automatically before the webhook is sent. The API is for selective rejection when auto-reject is disabled.

**Webhook Integration Pattern:**

1. Enable `call.offer` in `WHATSAPP_WEBHOOK_EVENTS`
2. Set up a webhook receiver endpoint in your application
3. When a `call.offer` event arrives, apply your business logic
4. If the call should be rejected, call `POST /call/reject` with the values from the webhook payload
5. The call is rejected on WhatsApp, and the caller sees a "declined" status

## Media Messages

### Image Message

When `WHATSAPP_AUTO_DOWNLOAD_MEDIA` is enabled, media is downloaded and `image` contains the file path.
When disabled, `image` contains an object with the URL.

If a caption is present, it is included in the top-level `body` field (consistent with text messages).
When auto-download is enabled and a caption exists, `image` becomes an object with `path` and `caption`.
When no caption exists, `image` remains a plain file path string for backward compatibility.

With auto-download enabled (no caption):

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6A3",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2025-07-13T11:05:51Z",
    "image": "statics/media/1752404751-ad9e37ac-c658-4fe5-8d25-ba4a3f4d58fd.jpeg"
  }
}
```

With auto-download enabled (with caption):

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6A3",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2025-07-13T11:05:51Z",
    "body": "Check this out!",
    "image": {
      "path": "statics/media/1752404751-ad9e37ac-c658-4fe5-8d25-ba4a3f4d58fd.jpeg",
      "caption": "Check this out!"
    }
  }
}
```

With auto-download disabled:

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6A3",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2025-07-13T11:05:51Z",
    "body": "Check this out!",
    "image": {
      "url": "https://mmg.whatsapp.net/...",
      "caption": "Check this out!"
    }
  }
}
```

### Video Message

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6A4",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2025-07-13T11:07:24Z",
    "body": "Watch this!",
    "video": {
      "path": "statics/media/1752404845-b9393cd1-8546-4df9-8a60-ee3276036aba.mp4",
      "caption": "Watch this!"
    }
  }
}
```

### Audio Message

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6A5",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2023-10-15T10:55:00Z",
    "audio": "statics/media/1752404905-b9393cd1-8546-4df9-8a60-ee3276036aba.ogg"
  }
}
```

### Document Message

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6A6",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2023-10-15T11:00:00Z",
    "body": "Monthly report",
    "document": {
      "path": "statics/media/1752404965-document.pdf",
      "caption": "Monthly report"
    }
  }
}
```

With auto-download disabled:

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6A6",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2023-10-15T11:00:00Z",
    "body": "Monthly report",
    "document": {
      "url": "https://mmg.whatsapp.net/...",
      "filename": "report.pdf"
    }
  }
}
```

### Sticker Message

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "446AC2BAF2061B53E24CA526DBDFBD4E",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2025-07-13T11:09:45Z",
    "sticker": "statics/media/1752404986-ff2464a6-c54c-4e6c-afde-c4c925ce3573.webp"
  }
}
```

### Video Note Message

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6A7",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2023-10-15T11:02:00Z",
    "video_note": "statics/media/1752404990-videonote.mp4"
  }
}
```

## Special Message Types

### Contact Message

When a user shares a single contact:

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "56B3DFF4994284634E7AAFEEF6F1A0A2",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2025-07-13T11:10:19Z",
    "contact": {
      "displayName": "3Care",
      "vcard": "BEGIN:VCARD\nVERSION:3.0\nN:;3Care;;;\nFN:3Care\nTEL;type=Mobile:+62 132\nEND:VCARD",
      "phone_number": "+62 132"
    }
  }
}
```

### Contacts Array Message

When a user shares multiple contacts at once (via WhatsApp's multi-contact share feature):

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "A1B2C3D4E5F6789012345678",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2025-07-13T11:10:19Z",
    "contacts_array": [
      {
        "displayName": "Alice",
        "vcard": "BEGIN:VCARD\nVERSION:3.0\nN:;Alice;;;\nFN:Alice\nTEL;type=Mobile:+62 812 3456 7890\nEND:VCARD",
        "phone_number": "+62 812 3456 7890"
      },
      {
        "displayName": "Bob",
        "vcard": "BEGIN:VCARD\nVERSION:3.0\nN:;Bob;;;\nFN:Bob\nTEL;type=Mobile:+62 813 9876 5432\nEND:VCARD",
        "phone_number": "+62 813 9876 5432"
      }
    ]
  }
}
```

> **Note:** WhatsApp uses `ContactMessage` (field 4) for a single contact and `ContactsArrayMessage` (field 13) for multiple contacts. A single contact produces `"contact"`, while multiple contacts produce `"contacts_array"`.

### Location Message

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6A9",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2023-10-15T11:15:00Z",
    "location": {
      "degreesLatitude": -6.2088,
      "degreesLongitude": 106.8456,
      "name": "Jakarta, Indonesia",
      "address": "Central Jakarta, DKI Jakarta, Indonesia"
    }
  }
}
```

### Live Location Message

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "94D13237B4D7F33EE4A63228BBD79EC0",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2025-07-13T11:11:22Z",
    "live_location": {
      "degreesLatitude": -7.8050297,
      "degreesLongitude": 110.4549165
    }
  }
}
```

## Protocol Messages

### Message Deleted

Triggered when a message is deleted for the current user (DeleteForMe event).

```json
{
  "event": "message.deleted",
  "device_id": "628123456789@s.whatsapp.net",
  "payload": {
    "deleted_message_id": "3EB0C127D7BACC83D6A1",
    "timestamp": "2025-07-13T11:12:00Z",
    "from": "628987654321@s.whatsapp.net",
    "chat_id": "628987654321@s.whatsapp.net",
    "original_content": "Hello, how are you?",
    "original_sender": "628987654321@s.whatsapp.net",
    "original_timestamp": "2025-07-13T10:30:00Z",
    "was_from_me": false
  }
}
```

**Fields:**

| **Field**                      | **Type** | **Description**                                       |
|--------------------------------|----------|-------------------------------------------------------|
| `payload.deleted_message_id`   | string   | ID of the deleted message                             |
| `payload.timestamp`            | string   | RFC3339 timestamp when the delete event occurred      |
| `payload.from`                 | string   | JID of the user who deleted the message               |
| `payload.chat_id`              | string   | Chat identifier where the message was deleted         |
| `payload.original_content`     | string   | Original message content (if available from storage)  |
| `payload.original_sender`      | string   | Original sender of the deleted message                |
| `payload.original_timestamp`   | string   | Original message timestamp                            |
| `payload.was_from_me`          | boolean  | Whether the deleted message was sent by current user  |
| `payload.original_media_type`  | string   | Media type if the message contained media (optional)  |
| `payload.original_filename`    | string   | Filename if the message contained media (optional)    |

### Message Revoked

```json
{
  "event": "message.revoked",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "F4062F2BBCB19B7432195AD7080DA4E2",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2025-07-13T11:13:30Z",
    "is_from_me": true,
    "revoked_message_id": "94D13237B4D7F33EE4A63228BBD79EC0",
    "revoked_from_me": true,
    "revoked_chat": "628987654321@s.whatsapp.net"
  }
}
```

### Message Edited

When a message is edited, the webhook includes the original message ID to track which message was modified.

```json
{
  "event": "message.edited",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "D6271D8223A05B4DA6AE9FE3CD632543",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2025-07-13T11:14:19Z",
    "is_from_me": false,
    "original_message_id": "94D13237B4D7F33EE4A63228BBD79EC0",
    "body": "Updated message text"
  }
}
```

**Fields:**

- `original_message_id`: The ID of the message that was edited (use this to update the correct message in your database)
- `body`: The new text content after editing
- `id`: The ID of the edit event itself (different from the original message ID)

## Special Flags

### View Once Message

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6B2",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2023-10-15T11:40:00Z",
    "image": "statics/media/1752405060-image.jpeg",
    "view_once": true
  }
}
```

### Forwarded Message

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6B3",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "John Doe",
    "timestamp": "2023-10-15T11:45:00Z",
    "body": "This is a forwarded message",
    "forwarded": true
  }
}
```

### Meta Ads Referral (Click-to-WhatsApp)

When a conversation starts from a Meta Click-to-WhatsApp ad, the first inbound message includes ad attribution metadata in the `referral` field. This is extracted from the WhatsApp protocol's `contextInfo.externalAdReply`.

```json
{
  "event": "message",
  "device_id": "628987654321@s.whatsapp.net",
  "payload": {
    "id": "3EB0C127D7BACC83D6B4",
    "chat_id": "628987654321@s.whatsapp.net",
    "from": "628123456789@s.whatsapp.net",
    "from_name": "Jane Customer",
    "timestamp": "2023-10-15T12:00:00Z",
    "is_from_me": false,
    "body": "Hello! I would like more information about the property.",
    "referral": {
      "ctwa_clid": "FAKE_CLID_abc123xyz",
      "source_url": "https://fb.me/fake-ad-link",
      "source_id": "123456789012345",
      "ref": "landing_page_01",
      "source_app": "facebook",
      "media_type": "IMAGE",
      "ad_title": "Your Dream Farm",
      "ad_body": "Discover exclusive rural properties in the countryside.",
      "thumbnail_url": "https://example.com/ad-thumbnail.jpg",
      "original_image_url": "https://example.com/ad-image.jpg",
      "show_ad_attribution": true,
      "click_to_whatsapp_call": false,
      "contains_auto_reply": false,
      "automated_greeting_message_shown": true,
      "greeting_message_body": "Hello! I want to know more about this ad.",
      "source_type": "ad",
      "ad_type": "CTWA"
    }
  }
}
```

**Referral fields** (all optional, only present when the message originated from a Meta ad):

| **Field**                          | **Type** | **Description**                                                    |
|------------------------------------|----------|--------------------------------------------------------------------|
| `ctwa_clid`                        | string   | Meta Click-to-WhatsApp click ID for ad attribution                 |
| `source_url`                       | string   | Landing page / ad destination URL                                  |
| `source_id`                        | string   | Meta ad creative or ad set ID                                      |
| `ref`                              | string   | The `ref` parameter set on the WhatsApp button in the ad           |
| `source_app`                       | string   | Origin platform: `"facebook"` or `"instagram"`                     |
| `media_type`                       | string   | Ad creative media type: `"NONE"`, `"IMAGE"`, or `"VIDEO"`         |
| `ad_title`                         | string   | Ad creative title                                                  |
| `ad_body`                          | string   | Ad creative description/body text                                  |
| `thumbnail_url`                    | string   | URL of the ad thumbnail image                                      |
| `original_image_url`               | string   | URL of the original ad image                                       |
| `media_url`                        | string   | URL of the ad media                                                |
| `show_ad_attribution`              | boolean  | Whether to show the ad attribution badge                           |
| `contains_auto_reply`              | boolean  | Whether the ad has a pre-filled auto-reply message                 |
| `automated_greeting_message_shown` | boolean  | Whether the automated greeting was shown to the user               |
| `greeting_message_body`            | string   | Body text of the automated greeting message                        |
| `click_to_whatsapp_call`           | boolean  | Whether this is a Click-to-WhatsApp Call ad (vs chat)              |
| `source_type`                      | string   | Source type (e.g., `"ad"`)                                         |
| `ad_type`                          | string   | Ad type: `"CTWA"` (Click-to-WhatsApp) or `"CAWC"` (Click-to-Call) |

## Integration Guide

### Setting Up Webhook Endpoint

1. **Configure webhook URL(s)**:

   ```bash
   ./whatsapp rest --webhook="https://yourapp.com/webhook"
   ```

2. **Set webhook secret**:

   ```bash
   ./whatsapp rest --webhook-secret="your-secret-key"
   ```

3. **Multiple webhooks**:

   ```bash
   ./whatsapp rest --webhook="https://app1.com/webhook,https://app2.com/webhook"
   ```

### Webhook Endpoint Implementation (Express.js)

```javascript
const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.raw({type: 'application/json'}));

app.post('/webhook', (req, res) => {
    const signature = req.headers['x-hub-signature-256'];
    const payload = req.body;
    const secret = 'your-secret-key';

    // Verify signature
    if (!verifyWebhookSignature(payload, signature, secret)) {
        return res.status(401).send('Unauthorized');
    }

    // Parse and process webhook data
    const data = JSON.parse(payload);
    console.log('Received webhook:', data);

    // Handle different event types based on data.event
    switch (data.event) {
        case 'message':
            console.log('New message:', {
                id: data.payload.id,
                from: data.payload.from,
                body: data.payload.body,
                chat_id: data.payload.chat_id
            });
            break;

        case 'message.reaction':
            console.log('Reaction:', {
                reaction: data.payload.reaction,
                reacted_message_id: data.payload.reacted_message_id
            });
            break;

        case 'message.revoked':
            console.log('Message revoked:', data.payload.revoked_message_id);
            break;

        case 'message.edited':
            console.log('Message edited:', {
                original_id: data.payload.original_message_id,
                new_body: data.payload.body
            });
            break;

        case 'message.ack':
            console.log(`Message ${data.payload.receipt_type}:`, {
                chat_id: data.payload.chat_id,
                message_ids: data.payload.ids,
                description: data.payload.receipt_type_description
            });
            break;

        case 'group.participants':
            console.log(`Group ${data.payload.type} event:`, {
                chat_id: data.payload.chat_id,
                affected_users: data.payload.jids
            });
            break;

        case 'newsletter.joined':
            console.log('Joined newsletter:', {
                newsletter_id: data.payload.newsletter_id,
                name: data.payload.name
            });
            break;

        case 'newsletter.left':
            console.log('Left newsletter:', {
                newsletter_id: data.payload.newsletter_id,
                role: data.payload.role
            });
            break;

        case 'newsletter.message':
            console.log('Newsletter message:', {
                newsletter_id: data.payload.newsletter_id,
                messages: data.payload.messages
            });
            break;

        case 'newsletter.mute':
            console.log('Newsletter mute changed:', {
                newsletter_id: data.payload.newsletter_id,
                mute: data.payload.mute
            });
            break;

        case 'call.offer':
            console.log('Incoming call:', {
                call_id: data.payload.call_id,
                from: data.payload.from,
                auto_rejected: data.payload.auto_rejected,
                platform: data.payload.remote_platform
            });
            break;
    }

    res.status(200).send('OK');
});

function verifyWebhookSignature(payload, signature, secret) {
    const expectedSignature = crypto
        .createHmac('sha256', secret)
        .update(payload, 'utf8')
        .digest('hex');

    const receivedSignature = signature.replace('sha256=', '');
    return crypto.timingSafeEqual(
        Buffer.from(expectedSignature, 'hex'),
        Buffer.from(receivedSignature, 'hex')
    );
}

app.listen(3001, () => {
    console.log('Webhook server listening on port 3001');
});
```

### Error Handling

The webhook system includes retry logic with exponential backoff:

- **Timeout**: 10 seconds per request
- **Max Attempts**: 5 retries
- **Backoff**: Exponential (1s, 2s, 4s, 8s, 16s)

Ensure your webhook endpoint:

- Responds within 10 seconds
- Returns HTTP 2xx status for successful processing
- Handles duplicate events gracefully
- Validates signatures for security

## Configuration

### Environment Variables

```bash
# Single webhook URL
WHATSAPP_WEBHOOK=https://yourapp.com/webhook

# Multiple webhook URLs (comma-separated)
WHATSAPP_WEBHOOK=https://app1.com/webhook,https://app2.com/webhook

# Webhook secret for HMAC verification
WHATSAPP_WEBHOOK_SECRET=your-super-secret-key
```

### Command Line Flags

```bash
# Single webhook
./whatsapp rest --webhook="https://yourapp.com/webhook"

# Multiple webhooks
./whatsapp rest --webhook="https://app1.com/webhook,https://app2.com/webhook"

# Custom secret
./whatsapp rest --webhook-secret="your-secret-key"
```

## Best Practices

1. **Always verify signatures** to ensure webhook authenticity
2. **Handle duplicates** - the same event might be sent multiple times
3. **Process quickly** - respond within 10 seconds to avoid timeouts
4. **Log errors** for debugging webhook integration issues
5. **Use HTTPS** for webhook URLs to ensure secure transmission
6. **Store media files** locally if you need to process them later
7. **Implement proper error handling** for different event types

## Troubleshooting

### Common Issues

1. **Webhook not receiving events**:
    - Check webhook URL is accessible from the internet
    - Verify webhook configuration
    - Check firewall and network settings

2. **Signature verification fails**:
    - Ensure webhook secret matches configuration
    - Use raw request body for signature calculation
    - Check HMAC implementation

3. **Timeouts**:
    - Optimize webhook processing speed
    - Implement asynchronous processing
    - Return response quickly, process in background

4. **Missing media files**:
    - Check media storage path configuration
    - Ensure sufficient disk space
    - Verify file permissions

### Debug Logging

Enable debug mode to see webhook logs:

```bash
./whatsapp rest --debug=true --webhook="https://yourapp.com/webhook"
```

This will show detailed logs of webhook delivery attempts and errors.
