Get notified when customers send and receive WhatsApp messages.

Configure webhooks

Use the Platform API to create webhooks for your customers:
const webhook = await fetch('https://app.kapso.ai/api/v1/whatsapp_webhooks', {
  method: 'POST',
  headers: { 'X-API-Key': 'YOUR_API_KEY' },
  body: JSON.stringify({
    whatsapp_webhook: {
      whatsapp_config_id: 'config-123',
      url: 'https://your-app.com/webhooks/whatsapp',
      events: ['whatsapp.message.received'],
      secret_key: 'your-secret-key'
    }
  })
});
Your endpoint must:
  • Accept POST requests
  • Return 200 status within 10 seconds
  • Handle duplicate events (idempotent)

Available webhook events

The Platform API supports the following WhatsApp webhook events:
  • whatsapp.message.received - Incoming message from end user
  • whatsapp.message.sent - Message sent by your application
  • whatsapp.message.delivered - Message delivered to recipient
  • whatsapp.message.read - Message read by recipient
  • whatsapp.message.failed - Message delivery failed
  • whatsapp.conversation.created - New conversation started
  • whatsapp.conversation.ended - Conversation ended
For complete webhook documentation including payload formats, message types, and message buffering, see API & Webhooks.

Webhook security

Verify requests using the signature header:
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-kapso-signature'];
  const isValid = verifyWebhook(
    JSON.stringify(req.body),
    signature,
    process.env.WEBHOOK_SECRET
  );
  
  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process webhook
  console.log('Event:', req.body.event);
  res.status(200).send('OK');
});

Retry policy

Failed webhooks (non-200 status) are retried:
  • 3 attempts total
  • Fast exponential backoff: 10s, 40s, 90s
  • Total time to failure: ~2.5 minutes
  • After max retries, batched messages fall back to individual delivery

Testing webhooks

Use ngrok or Cloudflare tunnel for local testing:
# Option 1: ngrok
ngrok http 3000
# Use the generated URL: https://abc123.ngrok.io/webhook

# Option 2: Cloudflare tunnel
brew install cloudflared
cloudflared tunnel --url http://localhost:3000
# Use the generated URL: https://random-name.trycloudflare.com/webhook

Detecting customer connection

When a customer successfully connects WhatsApp through a setup link, they’re redirected to your success_redirect_url with the WhatsApp configuration ID:
// Create setup link with redirect URLs
const setupLink = await fetch('https://app.kapso.ai/api/v1/setup_links', {
  method: 'POST',
  headers: { 'X-API-Key': 'YOUR_API_KEY' },
  body: JSON.stringify({
    setup_link: {
      customer_id: 'customer-123',
      success_redirect_url: 'https://your-app.com/whatsapp/success',
      failure_redirect_url: 'https://your-app.com/whatsapp/failed'
    }
  })
});

// Handle success redirect in your app
app.get('/whatsapp/success', async (req, res) => {
  const { whatsapp_config_id } = req.query;
  
  // Update your database
  await db.customers.update({
    whatsapp_config_id,
    connected_at: new Date()
  });
  
  // Show success page to customer
  res.render('whatsapp-connected');
});

Handling webhook events

Process incoming messages

app.post('/webhook', async (req, res) => {
  const { event, data } = req.body;
  
  if (event === 'whatsapp.message.received') {
    const { message, conversation, is_new_conversation } = data;
    
    if (is_new_conversation) {
      // Start new conversation flow
      await startConversation(conversation.id, message.from);
    }
    
    // Process the message
    await processMessage(message);
  }
  
  res.status(200).send('OK');
});

Track message status

app.post('/webhook', async (req, res) => {
  const { event, data } = req.body;
  
  if (event === 'whatsapp.message.delivered') {
    await updateMessageStatus(data.message.id, 'delivered');
  }
  
  if (event === 'whatsapp.message.read') {
    await updateMessageStatus(data.message.id, 'read');
  }
  
  if (event === 'whatsapp.message.failed') {
    await handleFailedMessage(data.message.id, data.message.error);
  }
  
  res.status(200).send('OK');
});