> ## Documentation Index
> Fetch the complete documentation index at: https://docs.buildwithchirp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# WhatsApp Webhooks Setup

> Configure webhooks to receive real-time WhatsApp notifications

Webhooks allow you to receive real-time HTTP notifications for WhatsApp events, including incoming messages and delivery status updates.

## Creating a Webhook

Create a webhook for your application to start receiving WhatsApp events.

**Via Dashboard**

1. Navigate to your application's Webhooks page
2. Click "Create Webhook"
3. Enter your webhook URL
4. Select WhatsApp-related events
5. (Optional) Add custom headers for authentication
6. Save the webhook

**Via API**

```bash icon="terminal" title="Create Webhook" theme={null}
curl -X POST https://api.buildwithchirp.com/v1/webhooks \
  -H "Authorization: Bearer YOUR_APP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/whatsapp",
    "events": [
      "messages.whatsapp.sent",
      "messages.whatsapp.delivered",
      "messages.whatsapp.read"
    ],
    "headers": {
      "X-Webhook-Secret": "your-secret-key"
    }
  }'
```

## WhatsApp Events

| Event                         | Description                        |
| ----------------------------- | ---------------------------------- |
| `messages.whatsapp.received`  | Inbound WhatsApp message received  |
| `messages.whatsapp.sent`      | Message sent to WhatsApp servers   |
| `messages.whatsapp.delivered` | Message delivered to user's device |
| `messages.whatsapp.read`      | Message read by the user           |
| `messages.whatsapp.failed`    | Message delivery failed            |

## Event Payloads

### Message Received

```json icon="json" title="messages.whatsapp.received" theme={null}
{
  "event": "messages.whatsapp.received",
  "data": {
    "id": "msg_wa_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
    "from": "+15551234567",
    "to": "+15559876543",
    "text": "Hello!",
    "type": "text",
    "receivedAt": "2024-01-15T12:00:00.000Z"
  }
}
```

### Message Sent

```json icon="json" title="messages.whatsapp.sent" theme={null}
{
  "event": "messages.whatsapp.sent",
  "data": {
    "id": "msg_wa_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
    "status": "sent",
    "sentAt": "2024-01-15T12:00:00.000Z"
  }
}
```

### Message Delivered

```json icon="json" title="messages.whatsapp.delivered" theme={null}
{
  "event": "messages.whatsapp.delivered",
  "data": {
    "id": "msg_wa_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
    "status": "delivered",
    "deliveredAt": "2024-01-15T12:00:05.000Z"
  }
}
```

### Message Read

```json icon="json" title="messages.whatsapp.read" theme={null}
{
  "event": "messages.whatsapp.read",
  "data": {
    "id": "msg_wa_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
    "status": "read",
    "readAt": "2024-01-15T12:00:10.000Z"
  }
}
```

### Message Failed

```json icon="json" title="messages.whatsapp.failed" theme={null}
{
  "event": "messages.whatsapp.failed",
  "data": {
    "id": "msg_wa_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
    "status": "failed",
    "failedAt": "2024-01-15T12:00:05.000Z",
    "error": {
      "type": "provider_error",
      "code": "message_undeliverable",
      "message": "Re-engagement message was not delivered",
      "provider": {
        "source": "meta",
        "code": 131047,
        "message": "Re-engagement message: Re-engagement message was not delivered"
      }
    }
  }
}
```

The `error` object uses the same format as [API error responses](/api_reference/errors), making it easy to handle errors consistently across your application. The `error.code` maps Meta's error codes to Chirp's standardized [error codes](/api_reference/error-codes), and the `error.provider` field preserves the original provider error details for debugging.

#### Enriched failures (`account_restricted`, `display_name_not_approved`)

When Meta's failure reason is vague — an `account_restricted` or `display_name_not_approved` status — Chirp calls Meta's [Health Status API](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/health-status) at ingest time and attaches the blocked entity plus Meta's suggested remediation to the webhook body. See [Health Status Enrichment](/whatsapp/error-codes#health-status-enrichment) for the full schema and handling guide.

```json icon="json" title="messages.whatsapp.failed (enriched)" highlight={12-40} theme={null}
{
  "event": "messages.whatsapp.failed",
  "data": {
    "id": "msg_wa_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
    "status": "failed",
    "failedAt": "2026-04-21T03:22:46.000Z",
    "error": {
      "type": "provider_error",
      "code": "account_restricted",
      "message": "Business Account locked",
      "additional_info": [
        "Appeal via Business Manager."
      ],
      "provider": {
        "source": "meta",
        "code": 131031,
        "message": "Business account has been locked.",
        "health_status": {
          "can_send_message": "BLOCKED",
          "entities": [
            {
              "entity_type": "PHONE_NUMBER",
              "id": "852850827913949",
              "can_send_message": "BLOCKED",
              "errors": [
                {
                  "error_code": 131031,
                  "error_description": "Account has been locked",
                  "possible_solution": "Appeal via Business Manager."
                }
              ]
            },
            {
              "entity_type": "WABA",
              "id": "1472866130489042",
              "can_send_message": "AVAILABLE"
            }
          ]
        }
      }
    }
  }
}
```

<Info>
  Enrichment is best-effort. If Chirp's Health Status lookup fails, you still get the base `error.code` and `error.message` — `additional_info` and `provider.health_status` will simply be absent. Treat them as hints, not invariants.
</Info>

## Response Requirements

Your webhook endpoint should:

1. **Respond within 5 seconds** - Return `200 OK` quickly
2. **Process asynchronously** - Don't block on long-running tasks
3. **Handle duplicates** - Events may be delivered multiple times

```javascript icon="javascript" title="Webhook Handler" theme={null}
app.post("/webhooks/whatsapp", (req, res) => {
  // Acknowledge immediately
  res.status(200).send("OK");

  // Process asynchronously
  processWhatsAppEvent(req.body).catch(console.error);
});

async function processWhatsAppEvent(payload) {
  const { event, data } = payload;

  switch (event) {
    case "messages.whatsapp.received":
      await handleIncomingMessage(data);
      break;
    case "messages.whatsapp.sent":
      await updateMessageStatus(data.id, "sent");
      break;
    case "messages.whatsapp.delivered":
      await updateMessageStatus(data.id, "delivered");
      break;
    case "messages.whatsapp.read":
      await updateMessageStatus(data.id, "read");
      break;
    case "messages.whatsapp.failed":
      await handleFailedMessage(data);
      break;
  }
}
```

## Security Best Practices

### Validate Request Origin

Use custom headers to verify requests are from Chirp:

```javascript icon="javascript" title="Validate Webhook" theme={null}
app.post("/webhooks/whatsapp", (req, res) => {
  const secret = req.headers["x-webhook-secret"];

  if (secret !== process.env.WEBHOOK_SECRET) {
    return res.status(401).send("Unauthorized");
  }

  // Process webhook...
  res.status(200).send("OK");
});
```

### Use HTTPS

Always use HTTPS URLs for webhook endpoints. HTTP is not supported.

## Retry Behavior

If your webhook fails (non-200 status or timeout):

* Chirp retries delivery with exponential backoff
* Maximum 5 retry attempts
* Failed webhooks are logged in your dashboard

## Testing Webhooks

**Use the Playground**

1. Configure a webhook on your test application
2. Send test messages using the Playground
3. Your webhook receives events in real-time
4. View webhook delivery logs in the dashboard

**Local Development**

Use tunneling tools for local testing:

* **ngrok** - Create public URLs for local servers
* **webhook.site** - Inspect webhook payloads
* **localtunnel** - Simple local tunneling

## Managing Webhooks

```bash icon="terminal" title="List Webhooks" theme={null}
curl https://api.buildwithchirp.com/v1/webhooks \
  -H "Authorization: Bearer YOUR_APP_KEY"
```

```bash icon="terminal" title="Update Webhook" theme={null}
curl -X PUT https://api.buildwithchirp.com/v1/webhooks/{webhookId} \
  -H "Authorization: Bearer YOUR_APP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["messages.whatsapp.delivered"]
  }'
```

```bash icon="terminal" title="Delete Webhook" theme={null}
curl -X DELETE https://api.buildwithchirp.com/v1/webhooks/{webhookId} \
  -H "Authorization: Bearer YOUR_APP_KEY"
```
