Skip to main content
Get notified when customers send and receive WhatsApp messages.

Project webhooks vs WhatsApp webhooks

Kapso supports two types of webhooks:
  • Project webhooks: Project-wide events (e.g., when customers connect WhatsApp)
  • WhatsApp webhooks: Message and conversation events for specific WhatsApp configurations

Configure project webhooks

  1. Open the sidebar and click Webhooks
  2. Go to the Project webhooks tab
  3. Click Add Webhook
  4. Enter your HTTPS endpoint URL
  5. Copy the auto-generated secret key for signature verification

Project webhook events

whatsapp.config.created

Fires when a customer successfully connects their WhatsApp through a setup link. Use case: Detect when customers complete onboarding, update your database, trigger welcome flows. Payload structure:
{
  "whatsapp_config": {
    "id": "770e8400-e29b-41d4-a716-446655440002",
    "name": "Production WhatsApp",
    "phone_number_id": "123456789012345",
    "business_account_id": "987654321098765",
    "is_coexistence": false,
    "inbound_processing_enabled": true,
    "calls_enabled": false,
    "webhook_verified_at": "2024-01-10T08:00:00Z",
    "created_at": "2024-01-10T08:00:00Z",
    "updated_at": "2024-01-15T14:30:00Z",
    "customer_id": "880e8400-e29b-41d4-a716-446655440003",
    "display_name": "Production WhatsApp",
    "display_phone_number": "+1 (555) 123-4567",
    "display_phone_number_normalized": "15551234567"
  },
  "project": {
    "id": "990e8400-e29b-41d4-a716-446655440004",
    "name": "My SaaS Platform",
    "description": "Customer communications platform",
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2024-01-15T14:30:00Z"
  },
  "customer": {
    "id": "880e8400-e29b-41d4-a716-446655440003",
    "name": "Acme Corporation",
    "external_customer_id": "acme-corp-123",
    "created_at": "2024-01-08T10:00:00Z",
    "updated_at": "2024-01-15T14:30:00Z"
  },
  "setup_link": {
    "id": "aa0e8400-e29b-41d4-a716-446655440005",
    "status": "used",
    "created_at": "2024-01-15T14:00:00Z",
    "expires_at": "2024-02-14T14:00:00Z",
    "success_redirect_url": "https://your-app.com/onboarding/success",
    "failure_redirect_url": "https://your-app.com/onboarding/error",
    "allowed_connection_types": ["coexistence", "dedicated"],
    "theme_config": null,
    "provision_phone_number": false,
    "phone_number_area_code": null,
    "phone_number_country_isos": ["US"],
    "whatsapp_setup_status": "completed",
    "whatsapp_setup_error": null,
    "url": "https://setup.kapso.ai/s/abc123"
  },
  "phone_number": {
    "id": "bb0e8400-e29b-41d4-a716-446655440006",
    "phone_number": "+15551234567",
    "status": "active",
    "area_code": "555",
    "country_iso": "US",
    "country_dial_code": "1",
    "provisioned_at": "2024-01-15T14:25:00Z",
    "released_at": null,
    "display_number": "+1 (555) 123-4567"
  },
  "creation_context": {
    "sandbox": false,
    "coexistence": false
  }
}
Headers:
X-Webhook-Event: whatsapp.config.created
X-Webhook-Signature: <hmac-hex-signature>
X-Idempotency-Key: <unique-key-per-event>
Content-Type: application/json

WhatsApp 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',
      inactivity_minutes: 45  // Optional: for conversation.inactive event (default: 60)
    }
  })
});
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
  • whatsapp.conversation.inactive - No messages for configured duration (1-1440 minutes, default 60)
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-webhook-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

Two ways to detect when customers connect WhatsApp: Use the whatsapp.config.created event (see Project webhooks above).

2. Success redirect URL

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 { setup_link_id, status, 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');
});
I