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

# Webhooks overview

> Get real-time notifications for WhatsApp events

Kapso uses webhooks to push real-time notifications about your WhatsApp messages and conversations. All webhooks use HTTPS and deliver a JSON payload you can use in your application.

## What are webhooks?

Webhooks notify your application when events occur. You can use them to:

* Send automated replies when customers message you
* Update conversation status in your CRM
* Track message delivery and read receipts
* Trigger alerts when conversations go inactive
* Store events in your database for analytics

## Steps to receive webhooks

1. Create an endpoint to receive requests
2. Register your webhook
3. Verify signatures
4. Test your endpoint

## 1. Create an endpoint

Create a route in your application that accepts POST requests.

```javascript theme={null}
app.post('/webhooks/whatsapp', async (req, res) => {
  const event = req.headers['x-webhook-event'];
  const isBatch = req.headers['x-webhook-batch'] === 'true' || req.body.batch === true;
  const payloads = isBatch ? req.body.data : [req.body];

  console.log('Event:', event);
  console.log('Payload count:', payloads.length);

  for (const payload of payloads) {
    await processWebhookPayload(event, payload);
  }

  // Return 200 to acknowledge receipt
  res.status(200).send('OK');
});
```

Kapso sends the event name in `X-Webhook-Event`. Unbuffered webhooks send the event payload directly as the request body. When buffering is enabled for `whatsapp.message.received`, the body uses a batch envelope with `type`, `batch: true`, `data: [...]`, and `batch_info`.

Your endpoint must return `200 OK` within 10 seconds.

## 2. Register your webhook

Kapso supports two types of webhooks:

### Project webhooks

Project-wide events like WhatsApp connection lifecycle and workflow execution.
No message or conversation events here. Use a WhatsApp webhook per phone number.

**Setup:**

1. Open **Integrations → Webhooks**
2. Go to the **Platform webhooks** tab
3. Click **Add Webhook**
4. Enter your HTTPS endpoint URL
5. Copy the auto-generated secret key
6. Subscribe to events

### WhatsApp webhooks

Message and conversation events for specific WhatsApp numbers. Two webhook kinds available:

#### Kapso webhooks (default)

Event-based webhooks with Kapso payload format. Subscribe to specific events, use buffering, and receive structured payloads.

```javascript theme={null}
await fetch('https://api.kapso.ai/platform/v1/whatsapp/phone_numbers/{phone_number_id}/webhooks', {
  method: 'POST',
  headers: {
    'X-API-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    whatsapp_webhook: {
      kind: 'kapso', // optional, this is the default
      url: 'https://your-app.com/webhooks/whatsapp',
      events: ['whatsapp.message.received'],
      secret_key: 'your-secret-key'
    }
  })
});
```

#### Meta webhooks

Receive the exact payload Meta sends. No event filtering, no buffering - just raw Meta webhook forwarding with an idempotency key for deduplication.

```javascript theme={null}
await fetch('https://api.kapso.ai/platform/v1/whatsapp/phone_numbers/{phone_number_id}/webhooks', {
  method: 'POST',
  headers: {
    'X-API-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    whatsapp_webhook: {
      kind: 'meta',
      url: 'https://your-app.com/webhooks/whatsapp-meta',
      active: true
    }
  })
});
```

Meta webhooks include an `X-Idempotency-Key` header (SHA256 hash of the payload) for deduplication. Only one meta webhook is allowed per phone number.

## 3. Verify signatures

Always verify webhook signatures to ensure requests come from Kapso.

```javascript theme={null}
const crypto = require('crypto');

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

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

app.post('/webhooks/whatsapp', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const isValid = verifyWebhook(req.body, signature, process.env.WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

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

See [Security](/docs/platform/webhooks/security) for detailed verification guide.

## 4. Test your endpoint

Use ngrok or Cloudflare tunnel for local testing:

```bash theme={null}
# Option 1: ngrok
ngrok http 3000

# Option 2: Cloudflare tunnel
brew install cloudflared
cloudflared tunnel --url http://localhost:3000
```

Register the generated HTTPS URL in your webhook configuration.

## Webhook headers

### Kapso webhooks

```
X-Webhook-Event: whatsapp.message.received
X-Webhook-Signature: <hmac-sha256-hex>
X-Idempotency-Key: <unique-uuid>
X-Webhook-Payload-Version: v2
Content-Type: application/json
```

Batched webhooks also include:

```
X-Webhook-Batch: true
X-Batch-Size: 2
```

### Meta webhooks

```
Content-Type: application/json
X-Idempotency-Key: <sha256-of-payload>
```

Meta webhooks forward the exact payload received from Meta, without modification.

## More webhook docs

<CardGroup cols={2}>
  <Card title="Event types" icon="list" href="/docs/platform/webhooks/event-types">
    See available webhook events and payload examples
  </Card>

  <Card title="Security" icon="shield-check" href="/docs/platform/webhooks/security">
    Verify signatures and secure your endpoint
  </Card>

  <Card title="Advanced" icon="sliders" href="/docs/platform/webhooks/advanced">
    Understand buffering, ordering, and retries
  </Card>

  <Card title="Legacy webhooks" icon="clock-rotate-left" href="/docs/platform/webhooks/legacy">
    Reference the older webhook format and migration path
  </Card>
</CardGroup>

## FAQ

<AccordionGroup>
  <Accordion title="What is the retry schedule?">
    If Kapso doesn't receive a 200 response, webhooks are retried automatically:

    * 10 seconds
    * 40 seconds
    * 90 seconds

    Total time: \~2.5 minutes. After max retries, batched messages fall back to individual delivery.

    See [Advanced](/docs/platform/webhooks/advanced) for details.
  </Accordion>

  <Accordion title="How do I handle duplicate events?">
    Use the `X-Idempotency-Key` header to track processed events:

    ```javascript theme={null}
    const processedKeys = new Set();

    app.post('/webhooks', (req, res) => {
      const idempotencyKey = req.headers['x-idempotency-key'];

      if (processedKeys.has(idempotencyKey)) {
        return res.status(200).send('Already processed');
      }

      // Process event
      processedKeys.add(idempotencyKey);
      res.status(200).send('OK');
    });
    ```
  </Accordion>

  <Accordion title="What about v1 webhooks?">
    New webhooks default to v2. Existing v1 webhooks continue to work.

    See [Legacy webhooks](/docs/platform/webhooks/legacy) for migration guide.
  </Accordion>
</AccordionGroup>
