# Webhook Configuration Guide This guide provides detailed instructions for configuring webhooks for both Twilio (voice call handling) and Meta (WhatsApp message handling) in your General Bots integration. ## Overview The integration requires two separate webhook configurations: 1. **Twilio Voice Webhook** - Handles incoming verification calls and captures verification codes 2. **Meta WhatsApp Webhook** - Receives incoming WhatsApp messages and status updates ## Twilio Webhook Configuration ### Purpose The Twilio webhook is critical during the initial phone number verification phase. Since Twilio numbers don't support SMS verification, Meta must call your number and read a 6-digit code. Your webhook must: 1. Answer the incoming call from Meta 2. Capture the audio or DTMF tones (key presses) 3. Forward the verification code to your email or logging system ### Webhook URL Structure ``` POST https://your-domain.com/twilio/voice ``` ### Required HTTP Headers Twilio sends these headers with every webhook request: | Header | Description | Example | |--------|-------------|---------| | `X-Twilio-Signature` | Request signature for security | `RCYmLs...` | | `Content-Type` | Always `application/x-www-form-urlencoded` | - | ### Request Body Parameters When a call comes in, Twilio POSTs these parameters: | Parameter | Description | Example | |-----------|-------------|---------| | `CallSid` | Unique call identifier | `CA1234567890ABCDEF1234567890ABCDEF` | | `From` | Caller's phone number | `+1234567890` (Meta's verification number) | | `To` | Your Twilio number | `+553322980098` | | `CallStatus` | Current call status | `ringing` | | `Direction` | Call direction | `inbound` | ### TwiML Response Format Your webhook must respond with TwiML (Twilio Markup Language) XML: ```xml Please enter your verification code followed by the pound sign. https://twimlets.com/voicemail?Email=your-email@example.com ``` ### Implementation Examples #### Node.js/Express ```javascript const express = require('express'); const twilio = require('twilio'); const app = express(); app.post('/twilio/voice', (req, res) => { const twiml = new twilio.twiml.VoiceResponse(); const gather = twiml.gather({ action: '/twilio/gather', method: 'POST', numDigits: 6, timeout: 10 }); gather.say({ voice: 'alice', language: 'pt-BR' }, 'Please enter your verification code followed by the pound key.'); // Fallback to voicemail if no input twiml.redirect('https://twimlets.com/voicemail?Email=your-email@example.com'); res.type('text/xml'); res.send(twiml.toString()); }); app.post('/twilio/gather', (req, res) => { const verificationCode = req.body.Digits; console.log('WhatsApp Verification Code:', verificationCode); // Send email notification sendEmail({ to: 'your-email@example.com', subject: 'WhatsApp Verification Code', body: `Your verification code is: ${verificationCode}` }); const twiml = new twilio.twiml.VoiceResponse(); twiml.say('Thank you. Your code has been received.'); res.type('text/xml'); res.send(twiml.toString()); }); app.listen(3000, () => { console.log('Twilio webhook server running on port 3000'); }); ``` #### Python/Flask ```python from flask import Flask, request, Response from twilio.twiml.voice_response import VoiceResponse, Gather import smtplib app = Flask(__name__) @app.route('/twilio/voice', methods=['POST']) def voice_webhook(): response = VoiceResponse() gather = Gather( action='/twilio/gather', method='POST', num_digits=6, timeout=10 ) gather.say( 'Please enter your verification code followed by the pound key.', voice='alice', language='pt-BR' ) response.append(gather) # Fallback to voicemail response.redirect('https://twimlets.com/voicemail?Email=your-email@example.com') return Response(str(response), mimetype='text/xml') @app.route('/twilio/gather', methods=['POST']) def gather_webhook(): verification_code = request.form.get('Digits') print(f'WhatsApp Verification Code: {verification_code}') # Send email notification send_email( to='your-email@example.com', subject='WhatsApp Verification Code', body=f'Your verification code is: {verification_code}' ) response = VoiceResponse() response.say('Thank you. Your code has been received.') return Response(str(response), mimetype='text/xml') def send_email(to, subject, body): # Implement email sending logic pass if __name__ == '__main__': app.run(port=3000) ``` #### BASIC (General Bots) ```basic REM Twilio Voice Webhook Handler ON WEBHOOK POST TO "/twilio/voice" DO REM Create TwiML response LET TWIML$ = "" TWIML$ = TWIML$ + "" TWIML$ = TWIML$ + "" TWIML$ = TWIML$ + "" TWIML$ = TWIML$ + "Please enter your verification code followed by the pound sign." TWIML$ = TWIML$ + "" TWIML$ = TWIML$ + "" TWIML$ = TWIML$ + "https://twimlets.com/voicemail?Email=your-email@example.com" TWIML$ = TWIML$ + "" REM Set response content type SET RESPONSE HEADER "Content-Type" TO "text/xml" PRINT TWIML$ END ON REM Gather Handler (receives the DTMF input) ON WEBHOOK POST TO "/twilio/gather" DO REM Get the digits entered LET CODE$ = GET FORM VALUE "Digits" REM Log the verification code LOG "WhatsApp Verification Code: " + CODE$ REM Send email notification SEND MAIL TO "your-email@example.com" WITH SUBJECT "WhatsApp Verification Code" AND BODY "Your verification code is: " + CODE$ REM Create confirmation TwiML LET TWIML$ = "" TWIML$ = TWIML$ + "" TWIML$ = TWIML$ + "Thank you. Your code has been received." TWIML$ = TWIML$ + "" SET RESPONSE HEADER "Content-Type" TO "text/xml" PRINT TWIML$ END ON ``` ### Configuring Twilio 1. **Navigate to your phone number** - Go to Twilio Console > Phone Numbers > Active Numbers - Click on your purchased number 2. **Configure Voice Webhook** - Find "Voice & Fax" section - Set "A Call Comes In" to your webhook URL - Select HTTP POST method - Example: `https://your-domain.com/twilio/voice` 3. **Save changes** - Click "Save" to apply the configuration ### Webhook Security Verify that requests come from Twilio: ```javascript const twilio = require('twilio'); const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN); app.post('/twilio/voice', (req, res) => { const url = `https://${req.headers.host}${req.path}`; const signature = req.headers['x-twilio-signature']; if (client.validateRequest(url, req.body, signature)) { // Request is from Twilio, process it handleVoiceWebhook(req, res); } else { // Invalid signature res.status(403).send('Invalid signature'); } }); ``` ## Meta WhatsApp Webhook Configuration ### Purpose The Meta webhook receives: - Incoming WhatsApp messages from users - Message delivery status updates - Message read receipts - Webhook verification requests ### Webhook URL Structure ``` POST https://your-domain.com/webhooks/whatsapp ``` ### Required HTTP Headers | Header | Description | Example | |--------|-------------|---------| | `X-Hub-Signature-256` | HMAC SHA-256 signature | `sha256=...` | ### Webhook Verification When you first configure the webhook, Meta sends a GET request to verify your URL: ``` GET https://your-domain.com/webhooks/whatsapp?hub.verify_token=YOUR_TOKEN&hub.challenge=CHALLENGE_STRING ``` Your webhook must respond with the challenge: ```javascript app.get('/webhooks/whatsapp', (req, res) => { const mode = req.query['hub.mode']; const token = req.query['hub.verify_token']; const challenge = req.query['hub.challenge']; const VERIFY_TOKEN = '4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY'; if (mode === 'subscribe' && token === VERIFY_TOKEN) { console.log('Webhook verified'); res.status(200).send(challenge); } else { res.sendStatus(403); } }); ``` ### Message Payload Structure Meta sends JSON payloads with message data: ```json { "object": "whatsapp_business_account", "entry": [{ "id": "390727550789228", "changes": [{ "value": { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "+553322980098", "phone_number_id": "1158433381968079" }, "contacts": [{ "profile": { "name": "John Doe" }, "wa_id": "5511999999999" }], "messages": [{ "from": "5511999999999", "id": "wamid.HBgLNTE1OTk5OTk5OTk5FQIAERgSMzg1QTlCNkE2RTlFRTdFNDdF", "timestamp": "1704067200", "text": { "body": "Hello, how can I help you?" }, "type": "text" }] }, "field": "messages" }] }] } ``` ### Implementation Examples #### Node.js/Express ```javascript app.post('/webhooks/whatsapp', (req, res) => { try { const data = req.body; // Check if this is a WhatsApp message if (data.object === 'whatsapp_business_account') { data.entry.forEach(entry => { entry.changes.forEach(change => { if (change.field === 'messages') { const message = change.value.messages[0]; const from = message.from; const body = message.text.body; const messageId = message.id; console.log(`Received message from ${from}: ${body}`); // Process the message processWhatsAppMessage(from, body, messageId); } }); }); } res.status(200).send('OK'); } catch (error) { console.error('Webhook error:', error); res.status(500).send('Error'); } }); async function processWhatsAppMessage(from, body, messageId) { // Implement your bot logic here const response = await generateResponse(body); // Send reply await sendWhatsAppMessage(from, response); } ``` #### Python/Flask ```python @app.route('/webhooks/whatsapp', methods=['POST']) def whatsapp_webhook(): try: data = request.get_json() if data.get('object') == 'whatsapp_business_account': for entry in data.get('entry', []): for change in entry.get('changes', []): if change.get('field') == 'messages': message = change['value']['messages'][0] from_number = message['from'] body = message['text']['body'] message_id = message['id'] print(f"Received message from {from_number}: {body}") # Process the message process_whatsapp_message(from_number, body, message_id) return 'OK', 200 except Exception as e: print(f'Webhook error: {e}') return 'Error', 500 def process_whatsapp_message(from_number, body, message_id): # Implement your bot logic here response = generate_response(body) # Send reply send_whatsapp_message(from_number, response) ``` #### BASIC (General Bots) ```basic REM Meta WhatsApp Webhook Handler ON WEBHOOK POST TO "/webhooks/whatsapp" DO REM Get the JSON payload LET PAYLOAD$ = GET REQUEST BODY REM Parse the JSON (requires JSON parser library) LET OBJ = PARSE JSON PAYLOAD$ REM Check if this is a WhatsApp message IF GET JSON PATH OBJ, "object" = "whatsapp_business_account" THEN REM Get the message LET MESSAGE = GET JSON PATH OBJ, "entry[0].changes[0].value.messages[0]" REM Extract message details LET FROM$ = GET JSON PATH MESSAGE, "from" LET BODY$ = GET JSON PATH MESSAGE, "text.body" LET ID$ = GET JSON PATH MESSAGE, "id" REM Log the message LOG "WhatsApp message from " + FROM$ + ": " + BODY$ REM Process the message asynchronously SPAWN PROCESS WHATSAPP MESSAGE FROM$, BODY$, ID$ END IF REM Respond with 200 OK PRINT "OK" SET RESPONSE STATUS TO 200 END ON REM Message processor SUB PROCESS WHATSAPP MESSAGE FROM$, BODY$, ID$ REM Generate a response LET RESPONSE$ = GENERATE RESPONSE BODY$ REM Send the reply SEND WHATSAPP TO FROM$ WITH RESPONSE$ END SUB ``` ### Configuring Meta 1. **Navigate to WhatsApp API Setup** - Go to Meta for Developers > Your App > WhatsApp > API Setup 2. **Edit Webhook** - Click "Edit" next to Webhook - Enter your webhook URL: `https://your-domain.com/webhooks/whatsapp` - Enter your Verify Token: `4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY` - Click "Verify and Save" 3. **Subscribe to Webhook Fields** - Subscribe to: `messages` - This ensures you receive all incoming messages ### Webhook Security Implement signature verification: ```javascript const crypto = require('crypto'); app.post('/webhooks/whatsapp', (req, res) => { const signature = req.headers['x-hub-signature-256']; const payload = JSON.stringify(req.body); const appSecret = 'YOUR_APP_SECRET'; // From Meta dashboard const expectedSignature = 'sha256=' + crypto .createHmac('sha256', appSecret) .update(payload) .digest('hex'); if (signature !== expectedSignature) { console.error('Invalid webhook signature'); return res.status(403).send('Invalid signature'); } // Process the webhook processWebhook(req.body); res.status(200).send('OK'); }); ``` ## Testing Webhooks ### Using Ngrok for Local Development 1. **Install ngrok** ```bash npm install -g ngrok ``` 2. **Start your local server** ```bash node server.js ``` 3. **Start ngrok** ```bash ngrok http 3000 ``` 4. **Use the ngrok URL** - Your webhook URL: `https://abc123.ngrok.io/webhooks/whatsapp` ### Testing Twilio Webhook Use Twilio's webhook debugger: ```bash curl -X POST \ 'https://your-domain.com/twilio/voice' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'CallSid=CA123&From=+1234567890&To=+553322980098&CallStatus=ringing&Direction=inbound' ``` ### Testing Meta Webhook Use Meta's webhook testing tool: ```bash curl -X POST \ 'https://your-domain.com/webhooks/whatsapp' \ -H 'Content-Type: application/json' \ -H 'X-Hub-Signature-256: sha256=...' \ -d '{ "object": "whatsapp_business_account", "entry": [{ "id": "390727550789228", "changes": [{ "value": { "messaging_product": "whatsapp", "messages": [{ "from": "5511999999999", "text": {"body": "Test message"} }] }, "field": "messages" }] }] }' ``` ## Production Considerations ### High Availability - Deploy webhooks behind a load balancer - Implement retry logic for failed deliveries - Use a message queue (RabbitMQ, Redis) for async processing - Monitor webhook health and set up alerts ### Performance - Respond to webhooks quickly (< 3 seconds) - Process heavy operations asynchronously - Use worker queues for message processing - Implement rate limiting to prevent abuse ### Monitoring - Log all webhook requests and responses - Track delivery success rates - Monitor response times - Set up alerts for failures - Use tools like Sentry, Datadog, or New Relic ## Troubleshooting ### Common Issues **Problem: Webhook verification fails** - Ensure verify token matches exactly - Check that your endpoint returns the challenge - Verify your URL is publicly accessible **Problem: Messages not received** - Check webhook logs for errors - Verify subscription to `messages` field - Ensure your server is online and responding **Problem: Invalid signature errors** - Verify your app secret is correct - Check that you're computing the hash correctly - Ensure you're using the raw request body **Problem: Timeout errors** - Optimize your webhook handler - Move heavy processing to background jobs - Increase server capacity if needed ### Debugging Tools - **Twilio Debugger**: View all Twilio webhook attempts - **Meta Webhook Debugging**: Enable in app settings - **Ngrok Inspector**: Inspect requests in real-time - **Webhook.site**: Test webhooks without a server ## Next Steps - Set up persistent storage for message history - Implement message queue for reliability - Add webhook retry logic - Configure monitoring and alerting - Set up automated testing For more information on webhook security, see [Security Considerations](./README.md#security-considerations).