Webhooks allow you to receive real-time HTTP notifications for WhatsApp events, including incoming messages and delivery status updates.
Creating a Webhook
Create a webhook for your application to start receiving WhatsApp events.
Via Dashboard
Navigate to your application’s Webhooks page
Click “Create Webhook”
Enter your webhook URL
Select WhatsApp-related events
(Optional) Add custom headers for authentication
Save the webhook
Via API
curl -X POST https://api.buildwithchirp.com/v1/webhooks \
-H "Authorization: Bearer YOUR_APP_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/whatsapp",
"events": [
"messages.whatsapp.sent",
"messages.whatsapp.delivered",
"messages.whatsapp.read"
],
"headers": {
"X-Webhook-Secret": "your-secret-key"
}
}'
WhatsApp Events
Event Description messages.whatsapp.receivedInbound WhatsApp message received messages.whatsapp.sentMessage sent to WhatsApp servers messages.whatsapp.deliveredMessage delivered to user’s device messages.whatsapp.readMessage read by the user messages.whatsapp.failedMessage delivery failed
Event Payloads
Message Received
messages.whatsapp.received
{
"event" : "messages.whatsapp.received" ,
"data" : {
"id" : "msg_wa_2DbBs7GWhGvVNJGrDXr5RG0mBWI" ,
"from" : "+15551234567" ,
"to" : "+15559876543" ,
"text" : "Hello!" ,
"type" : "text" ,
"receivedAt" : "2024-01-15T12:00:00.000Z"
}
}
Message Sent
{
"event" : "messages.whatsapp.sent" ,
"data" : {
"id" : "msg_wa_2DbBs7GWhGvVNJGrDXr5RG0mBWI" ,
"status" : "sent" ,
"sentAt" : "2024-01-15T12:00:00.000Z"
}
}
Message Delivered
messages.whatsapp.delivered
{
"event" : "messages.whatsapp.delivered" ,
"data" : {
"id" : "msg_wa_2DbBs7GWhGvVNJGrDXr5RG0mBWI" ,
"status" : "delivered" ,
"deliveredAt" : "2024-01-15T12:00:05.000Z"
}
}
Message Read
{
"event" : "messages.whatsapp.read" ,
"data" : {
"id" : "msg_wa_2DbBs7GWhGvVNJGrDXr5RG0mBWI" ,
"status" : "read" ,
"readAt" : "2024-01-15T12:00:10.000Z"
}
}
Message Failed
{
"event" : "messages.whatsapp.failed" ,
"data" : {
"id" : "msg_wa_2DbBs7GWhGvVNJGrDXr5RG0mBWI" ,
"status" : "failed" ,
"failedAt" : "2024-01-15T12:00:05.000Z" ,
"error" : {
"type" : "provider_error" ,
"code" : "message_undeliverable" ,
"message" : "Re-engagement message was not delivered" ,
"provider" : {
"source" : "meta" ,
"code" : 131047 ,
"message" : "Re-engagement message: Re-engagement message was not delivered"
}
}
}
}
The error object uses the same format as API error responses , making it easy to handle errors consistently across your application. The error.code maps Meta’s error codes to Chirp’s standardized error codes , and the error.provider field preserves the original provider error details for debugging.
Enriched failures (account_restricted, display_name_not_approved)
When Meta’s failure reason is vague — an account_restricted or display_name_not_approved status — Chirp calls Meta’s Health Status API at ingest time and attaches the blocked entity plus Meta’s suggested remediation to the webhook body. See Health Status Enrichment for the full schema and handling guide.
messages.whatsapp.failed (enriched)
{
"event" : "messages.whatsapp.failed" ,
"data" : {
"id" : "msg_wa_2DbBs7GWhGvVNJGrDXr5RG0mBWI" ,
"status" : "failed" ,
"failedAt" : "2026-04-21T03:22:46.000Z" ,
"error" : {
"type" : "provider_error" ,
"code" : "account_restricted" ,
"message" : "Business Account locked" ,
"additional_info" : [
"Appeal via Business Manager."
],
"provider" : {
"source" : "meta" ,
"code" : 131031 ,
"message" : "Business account has been locked." ,
"health_status" : {
"can_send_message" : "BLOCKED" ,
"entities" : [
{
"entity_type" : "PHONE_NUMBER" ,
"id" : "852850827913949" ,
"can_send_message" : "BLOCKED" ,
"errors" : [
{
"error_code" : 131031 ,
"error_description" : "Account has been locked" ,
"possible_solution" : "Appeal via Business Manager."
}
]
},
{
"entity_type" : "WABA" ,
"id" : "1472866130489042" ,
"can_send_message" : "AVAILABLE"
}
]
}
}
}
}
}
Enrichment is best-effort. If Chirp’s Health Status lookup fails, you still get the base error.code and error.message — additional_info and provider.health_status will simply be absent. Treat them as hints, not invariants.
Response Requirements
Your webhook endpoint should:
Respond within 5 seconds - Return 200 OK quickly
Process asynchronously - Don’t block on long-running tasks
Handle duplicates - Events may be delivered multiple times
app . post ( "/webhooks/whatsapp" , ( req , res ) => {
// Acknowledge immediately
res . status ( 200 ). send ( "OK" );
// Process asynchronously
processWhatsAppEvent ( req . body ). catch ( console . error );
});
async function processWhatsAppEvent ( payload ) {
const { event , data } = payload ;
switch ( event ) {
case "messages.whatsapp.received" :
await handleIncomingMessage ( data );
break ;
case "messages.whatsapp.sent" :
await updateMessageStatus ( data . id , "sent" );
break ;
case "messages.whatsapp.delivered" :
await updateMessageStatus ( data . id , "delivered" );
break ;
case "messages.whatsapp.read" :
await updateMessageStatus ( data . id , "read" );
break ;
case "messages.whatsapp.failed" :
await handleFailedMessage ( data );
break ;
}
}
Security Best Practices
Validate Request Origin
Use custom headers to verify requests are from Chirp:
app . post ( "/webhooks/whatsapp" , ( req , res ) => {
const secret = req . headers [ "x-webhook-secret" ];
if ( secret !== process . env . WEBHOOK_SECRET ) {
return res . status ( 401 ). send ( "Unauthorized" );
}
// Process webhook...
res . status ( 200 ). send ( "OK" );
});
Use HTTPS
Always use HTTPS URLs for webhook endpoints. HTTP is not supported.
Retry Behavior
If your webhook fails (non-200 status or timeout):
Chirp retries delivery with exponential backoff
Maximum 5 retry attempts
Failed webhooks are logged in your dashboard
Testing Webhooks
Use the Playground
Configure a webhook on your test application
Send test messages using the Playground
Your webhook receives events in real-time
View webhook delivery logs in the dashboard
Local Development
Use tunneling tools for local testing:
ngrok - Create public URLs for local servers
webhook.site - Inspect webhook payloads
localtunnel - Simple local tunneling
Managing Webhooks
curl https://api.buildwithchirp.com/v1/webhooks \
-H "Authorization: Bearer YOUR_APP_KEY"
curl -X PUT https://api.buildwithchirp.com/v1/webhooks/{webhookId} \
-H "Authorization: Bearer YOUR_APP_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": ["messages.whatsapp.delivered"]
}'
curl -X DELETE https://api.buildwithchirp.com/v1/webhooks/{webhookId} \
-H "Authorization: Bearer YOUR_APP_KEY"