Overview
The WhatsApp webhook integration receives real-time notifications from Meta’s WhatsApp Business API. It handles webhook verification, signature validation, and message processing through a BullMQ queue system.
Endpoint: /api/whatsapp/webhook
Webhook Verification (GET)
Meta requires webhook verification during initial setup. This endpoint handles the verification challenge.
Request
GET /api/whatsapp/webhook?hub.mode=subscribe&hub.verify_token=YOUR_TOKEN&hub.challenge=CHALLENGE_STRING
Must be subscribe for webhook verification
Verification token configured in WHATSAPP_VERIFY_TOKEN environment variable
Random string provided by Meta to echo back
Response
Returns the challenge string if verification succeeds, or 403 Forbidden if token mismatch occurs.
Example Implementation
import whatsappWebhook from './backend/whatsapp/webhook.js' ;
app . use ( '/api/whatsapp' , whatsappWebhook );
Incoming Messages (POST)
Receives incoming WhatsApp messages from Meta’s webhook notifications.
Security: Signature Validation
All incoming webhooks must include an X-Hub-Signature-256 header for request integrity verification.
function validateSignature ( req , res , next ) {
const signature = req . headers [ 'x-hub-signature-256' ];
if ( ! signature ) {
console . warn ( "⚠️ Missing X-Hub-Signature-256" );
if ( process . env . NODE_ENV === 'production' || APP_SECRET ) {
return res . status ( 401 ). send ( "Missing Signature" );
}
}
if ( APP_SECRET && signature ) {
const elements = signature . split ( '=' );
const signatureHash = elements [ 1 ];
// Use raw body for HMAC verification
const payload = req . rawBody || JSON . stringify ( req . body );
const expectedHash = crypto . createHmac ( 'sha256' , APP_SECRET )
. update ( payload )
. digest ( 'hex' );
if ( signatureHash !== expectedHash ) {
console . error ( "❌ Invalid Signature" );
}
}
next ();
}
The signature is computed using HMAC-SHA256 with your WHATSAPP_APP_SECRET against the raw request body . Ensure req.rawBody is available via middleware.
Raw Body Middleware
To enable signature validation, configure Express to capture raw body:
app . use ( express . json ({
verify : ( req , res , buf ) => {
req . rawBody = buf ;
}
}));
Request Body
Must be whatsapp_business_account for WhatsApp events
Array of webhook entries containing message data Array of changes, typically contains one change object entry[0].changes[0].value
Contains the actual message data Array of messages received Unique WhatsApp message ID (wamid)
Message type (e.g., text, image, audio)
Message content (for text messages)
Unix timestamp of message
Response
The endpoint immediately returns 200 OK to prevent Meta webhook timeouts, then processes the message asynchronously via BullMQ.
Message Processing Flow
router . post ( '/webhook' , validateSignature , async ( req , res ) => {
// IMMEDIATE 200 OK
res . sendStatus ( 200 );
const body = req . body ;
if ( body . object === 'whatsapp_business_account' ) {
try {
const entry = body . entry ?.[ 0 ];
const changes = entry ?. changes ?.[ 0 ];
const value = changes ?. value ;
const message = value ?. messages ?.[ 0 ];
if ( message && message . type === 'text' ) {
const wamid = message . id ;
// Add to Queue for processing
await whatsappQueue . add ( 'process-message' , {
wamid ,
from: message . from ,
text: message . text . body ,
timestamp: message . timestamp
}, {
jobId: wamid , // Deduplication via BullMQ
removeOnComplete: true
});
console . log ( `📥 Queued message from ${ message . from } : ${ wamid } ` );
}
} catch ( error ) {
console . error ( "❌ Error encolando mensaje:" , error . message );
}
}
});
Messages are processed asynchronously after responding to Meta. This prevents webhook timeouts but requires proper error handling in the queue worker.
Example Webhook Payload
{
"object" : "whatsapp_business_account" ,
"entry" : [
{
"id" : "WHATSAPP_BUSINESS_ACCOUNT_ID" ,
"changes" : [
{
"value" : {
"messaging_product" : "whatsapp" ,
"metadata" : {
"display_phone_number" : "15551234567" ,
"phone_number_id" : "PHONE_NUMBER_ID"
},
"contacts" : [
{
"profile" : {
"name" : "Customer Name"
},
"wa_id" : "573001234567"
}
],
"messages" : [
{
"from" : "573001234567" ,
"id" : "wamid.HBgNNTczMDA..." ,
"timestamp" : "1677649200" ,
"text" : {
"body" : "Hola, necesito ayuda con mi pedido"
},
"type" : "text"
}
]
},
"field" : "messages"
}
]
}
]
}
Environment Variables
App secret from Meta for HMAC signature validation
Custom verification token for webhook setup
Redis connection URL for BullMQ queue (optional, defaults to localhost)
Error Handling
Scenario Status Code Response Missing signature (production) 401Missing SignatureInvalid signature 403Invalid SignatureVerification token mismatch 403(empty) Missing verification params 400(empty) Valid webhook 200(empty) Valid verification 200Challenge string
Next Steps
Queue System Learn how messages are processed via BullMQ
Session Management Understand session lifecycle and bot handover