v1 webhooks are legacy. Use v2 for all new integrations.
New webhooks default to v2. Existing v1 webhooks continue to work with no breaking changes.
v1 vs v2 differences
v1 (legacy)
- Includes nested
whatsapp_config object in all payloads
- Uses
whatsapp_config_id as the primary identifier
- Event:
whatsapp.config.created for connection lifecycle
- Message structure:
message_type, content fields
v2 (recommended)
- Phone-number-first with
phone_number_id at top level
- No nested
whatsapp_config object
- Event:
whatsapp.phone_number.created for connection lifecycle
- Message structure: Meta-style with
kapso extensions
Migration to v2
1. Check current version
Look for the X-Webhook-Payload-Version header in incoming webhooks:
app.post('/webhooks', (req, res) => {
const version = req.headers['x-webhook-payload-version'];
console.log('Webhook version:', version); // "v1" or "v2"
});
2. Update webhook handler
Before (v1):
const { whatsapp_config, conversation } = data;
const phoneNumberId = whatsapp_config.phone_number_id;
const configId = conversation.whatsapp_config_id;
After (v2):
const { phone_number_id, conversation } = data;
const phoneNumberId = phone_number_id;
const conversationPhoneNumberId = conversation.phone_number_id;
3. Handle message structure
Before (v1):
const { message } = data;
const content = message.content;
const type = message.message_type;
const direction = message.direction;
After (v2):
const { message } = data;
const content = message.text?.body || message.image?.caption;
const type = message.type;
const kapsoData = message.kapso;
const direction = kapsoData.direction;
v1 event: whatsapp.config.created
Lifecycle event fired when customer connects WhatsApp (v1 only).
For v2, use whatsapp.phone_number.created instead. See Connection detection.
Headers:
X-Webhook-Event: whatsapp.config.created
X-Webhook-Signature: <hmac-sha256-hex>
X-Idempotency-Key: <unique-uuid>
X-Webhook-Payload-Version: v1
Payload (abbreviated):
{
"whatsapp_config": {
"id": "770e8400-e29b-41d4-a716-446655440002",
"phone_number_id": "123456789012345",
"business_account_id": "987654321098765",
"customer_id": "880e8400-e29b-41d4-a716-446655440003",
"display_phone_number": "+1 (555) 123-4567"
},
"project": {
"id": "990e8400-e29b-41d4-a716-446655440004"
},
"customer": {
"id": "880e8400-e29b-41d4-a716-446655440003",
"external_customer_id": "acme-corp-123"
}
}
v1 payload examples
message.received
{
"message": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"message_type": "text",
"content": "Hello",
"direction": "inbound",
"status": "received",
"from": "+15551234567"
},
"conversation": {
"id": "770e8400-e29b-41d4-a716-446655440002",
"phone_number": "+15551234567",
"whatsapp_config_id": "880e8400-e29b-41d4-a716-446655440001"
},
"is_new_conversation": true,
"whatsapp_config": {
"id": "880e8400-e29b-41d4-a716-446655440001",
"phone_number_id": "123456789012345"
}
}
message.sent
{
"message": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"message_type": "text",
"content": "On my way",
"direction": "outbound",
"status": "sent"
},
"conversation": {
"id": "770e8400-e29b-41d4-a716-446655440002",
"whatsapp_config_id": "880e8400-e29b-41d4-a716-446655440001"
},
"whatsapp_config": {
"id": "880e8400-e29b-41d4-a716-446655440001",
"phone_number_id": "123456789012345"
}
}
conversation.created
{
"conversation": {
"id": "770e8400-e29b-41d4-a716-446655440002",
"phone_number": "+15551234567",
"whatsapp_config_id": "880e8400-e29b-41d4-a716-446655440001"
},
"whatsapp_config": {
"id": "880e8400-e29b-41d4-a716-446655440001",
"phone_number_id": "123456789012345"
}
}
Handling both versions
If you need to support both v1 and v2 webhooks during migration:
app.post('/webhooks', (req, res) => {
const version = req.headers['x-webhook-payload-version'];
const { event, data } = req.body;
if (event === 'whatsapp.message.received') {
let phoneNumberId, content, type;
if (version === 'v2') {
// v2 format
phoneNumberId = data.phone_number_id;
content = data.message.text?.body;
type = data.message.type;
} else {
// v1 format (legacy)
phoneNumberId = data.whatsapp_config.phone_number_id;
content = data.message.content;
type = data.message.message_type;
}
// Process with normalized data
await processMessage({ phoneNumberId, content, type });
}
res.status(200).send('OK');
});
Backward compatibility
v1 webhooks remain fully supported. You can migrate at your own pace:
- Existing v1 webhooks continue to work unchanged
- No breaking changes or deprecation timeline
- Both versions use the same signature verification (HMAC SHA256)
- Both versions support the same retry policy and ordering guarantees
Migrate to v2 when ready to benefit from simpler payload structure and Meta-compatible message format.