Skip to main content
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.
app.post('/webhooks/whatsapp', async (req, res) => {
  const { event, data } = req.body;

  console.log('Event:', event);
  console.log('Data:', data);

  // Return 200 to acknowledge receipt
  res.status(200).send('OK');
});
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 sidebar → Webhooks
  2. Go to Project 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.
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.
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.
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 for detailed verification guide.

4. Test your endpoint

Use ngrok or Cloudflare tunnel for local testing:
# 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.

FAQ

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 for details.
Use the X-Idempotency-Key header to track processed events:
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');
});
New webhooks default to v2. Existing v1 webhooks continue to work.See Legacy webhooks for migration guide.

Next steps

  • Event types - See all available events and payloads
  • Security - Detailed signature verification guide
  • Advanced - Message buffering, ordering, and retries