Troubleshooting Guide 🟡 BETA
This comprehensive guide helps you diagnose and resolve common issues when integrating WhatsApp Business API with Twilio phone numbers in General Bots.
Table of Contents
- Diagnostic Tools
- Verification Issues
- Webhook Problems
- API Errors
- Message Delivery Failures
- Twilio-Specific Issues
- Meta-Specific Issues
- Performance Issues
- Security Issues
Diagnostic Tools
Essential Commands
# Test webhook connectivity
curl -X POST https://your-webhook-url/webhooks/whatsapp \
-H "Content-Type: application/json" \
-d '{"test": true}'
# Check Meta API status
curl https://developers.facebook.com/status/
# Verify Twilio number configuration
twilio phone-numbers:list +553322980098
# Test Meta API connectivity
curl -X GET "https://graph.facebook.com/v18.0/YOUR_PHONE_NUMBER_ID" \
-H "Authorization: Bearer EAAQyour_api_key_here"
Log Locations
# General Bots logs
tail -f .gbot/logs/bot.log
# Webhook server logs (Node.js)
pm2 logs whatsapp-webhook
# Webhook server logs (Python)
journalctl -u whatsapp-webhook -f
# Twilio debugger
https://console.twilio.com/us1/develop/monitor/debugger
# Meta webhook debugging
https://developers.facebook.com/apps/YOUR_APP_ID/webhooks/
Verification Issues
Issue: Phone Number Verification Fails
Symptoms:
- Meta cannot verify your Twilio number
- “Verification failed” error after call completes
- No verification code received
Diagnosis:
# Check Twilio number has Voice capability
twilio phone-numbers:info +553322980098
# Test incoming call handling
# Call your Twilio number from another phone
# Check if webhook is triggered
Solutions:
-
Verify Voice Webhook Configuration
# Check webhook URL is correct curl -X POST https://your-domain.com/twilio/voice \ -d "CallSid=CA123&From=+1234567890&To=+553322980098" # Expected: TwiML XML response -
Verify Verification Method
- Ensure “Phone Call” is selected (not SMS)
- Twilio numbers don’t support SMS for verification
- Meta must call your number
-
Check TwiML Response
<!-- Your webhook must return valid TwiML --> <?xml version="1.0" encoding="UTF-8"?> <Response> <Gather action="https://your-domain.com/twilio/gather" method="POST" numDigits="6"> <Say>Please enter your verification code.</Say> </Gather> </Response> -
Test Webhook Locally
# Use ngrok for local testing ngrok http 3000 # Update Twilio webhook to ngrok URL # Test with actual Meta verification call
Issue: Verification Code Not Captured
Symptoms:
- Call completes but no code received
- Email not forwarded
- Code not logged
Diagnosis:
# Check webhook server logs
tail -f /var/log/whatsapp-webhook/app.log
# Verify Gather action is configured
# Test DTMF capture with a test call
Solutions:
-
Implement Email Forwarding
// Add to your gather handler app.post('/twilio/gather', (req, res) => { const code = req.body.Digits; // Send email sendEmail({ to: 'your-email@example.com', subject: 'Verification Code', body: `Code: ${code}` }); // Also log it console.log('Verification Code:', code); res.type('text/xml'); res.send('<Response><Say>Thank you</Say></Response>'); }); -
Use Voicemail Fallback
<Redirect> https://twimlets.com/voicemail?Email=your-email@example.com&Transcribe=true </Redirect> -
Add Logging
REM BASIC logging ON WEBHOOK POST TO "/twilio/gather" DO LET CODE$ = GET FORM VALUE "Digits" LOG "Verification Code: " + CODE$ PRINT "Code logged" END ON
Webhook Problems
Issue: Webhook Verification Fails
Symptoms:
- Meta webhook setup shows “Verification failed”
- “Challenge mismatch” error
- 403 Forbidden response
Diagnosis:
# Test webhook verification endpoint
curl "https://your-domain.com/webhooks/whatsapp?hub.verify_token=YOUR_TOKEN&hub.challenge=CHALLENGE"
# Expected response: The challenge string
Solutions:
-
Match Verify Token Exactly
// In config.csv whatsapp-webhook-verify-token,4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY // In your code const VERIFY_TOKEN = '4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY'; app.get('/webhooks/whatsapp', (req, res) => { const token = req.query['hub.verify_token']; if (token === VERIFY_TOKEN) { res.send(req.query['hub.challenge']); } else { res.sendStatus(403); } }); -
Return Challenge Correctly
- Must return the challenge as plain text
- Must respond with 200 OK status
- Must not include HTML or JSON formatting
-
Check URL Accessibility
# Ensure URL is publicly accessible curl -v https://your-domain.com/webhooks/whatsapp # Check firewall rules sudo ufw status # Verify SSL certificate openssl s_client -connect your-domain.com:443
Issue: Webhook Not Receiving Messages
Symptoms:
- Webhook endpoint configured but no messages received
- Messages appear in Meta inbox but not in bot
- No webhook logs
Diagnosis:
# Check webhook subscriptions in Meta dashboard
# Navigate to: WhatsApp > API Setup > Webhook > Subscriptions
# Verify webhook field subscription
curl -X GET "https://graph.facebook.com/v18.0/YOUR_APP_ID/subscriptions" \
-H "Authorization: Bearer EAAQyour_api_key_here"
Solutions:
-
Subscribe to Messages Field
In Meta Dashboard: 1. Go to WhatsApp > API Setup > Webhook 2. Click "Manage" webhook fields 3. Subscribe to: messages 4. Save changes -
Check Webhook URL
// Ensure correct webhook path app.post('/webhooks/whatsapp', (req, res) => { console.log('Webhook received:', JSON.stringify(req.body, null, 2)); res.status(200).send('OK'); }); -
Verify Response Time
- Webhook must respond within 3 seconds
- Process heavy operations asynchronously
- Return 200 OK immediately
app.post('/webhooks/whatsapp', (req, res) => { // Acknowledge immediately res.status(200).send('OK'); // Process asynchronously setImmediate(() => { processWebhook(req.body); }); });
API Errors
Issue: 401 Unauthorized
Symptoms:
- API calls return 401 status
- “Invalid access token” error
- Messages fail to send
Diagnosis:
# Test access token validity
curl -X GET "https://graph.facebook.com/v18.0/me" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Expected: JSON with app ID and name
# 401 means token is invalid or expired
Solutions:
-
Generate New Access Token
In Meta Dashboard: 1. Go to WhatsApp > API Setup 2. Click "Temporary Access Token" 3. Copy and update config.csv 4. Restart bot -
Check Token Format
# config.csv # Token must start with EAAQ... whatsapp-api-key,EAAQyour_api_key_here -
Verify Token Permissions
In Meta Dashboard: 1. Go to App Review > Permissions and Features 2. Ensure "WhatsApp" permission is granted 3. Check for any additional required permissions
Issue: 470 Message Rate Limit
Symptoms:
- Messages fail with 470 error
- “Rate limit exceeded” message
- Bulk sending stops working
Diagnosis:
# Check API rate limits
# WhatsApp Business API: 1000 messages/second per WABA
# Monitor message queue
tail -f .gbot/logs/message-queue.log
Solutions:
-
Implement Rate Limiting
class RateLimiter { constructor(maxRequests = 50, timeWindow = 60) { this.maxRequests = maxRequests; this.timeWindow = timeWindow; this.requests = []; } async send(whatsapp, to, message) { while (!this.canMakeRequest()) { await this.sleep(1000); } this.recordRequest(); return await whatsapp.sendText(to, message); } canMakeRequest() { const now = Date.now(); this.requests = this.requests.filter(t => now - t < this.timeWindow * 1000); return this.requests.length < this.maxRequests; } recordRequest() { this.requests.push(Date.now()); } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } const rateLimiter = new RateLimiter(50, 60); await rateLimiter.send(whatsapp, to, message); -
Use Message Queuing
REM BASIC message queuing SUB SEND WHATSAPP WITH RATE LIMIT TO NUMBER$, MESSAGE$ ADD TO QUEUE "whatsapp-outbound", NUMBER$ + "|" + MESSAGE$ END SUB -
Monitor Usage
In Meta Dashboard: 1. Go to WhatsApp > API Usage 2. Monitor message volume 3. Check for rate limit warnings
Issue: Invalid Phone Number Format
Symptoms:
- “Invalid phone number” error
- Messages not delivered
- 400 Bad Request
Diagnosis:
# Test phone number format
# Correct format: 5511999999999 (no +, no spaces, no dashes)
Solutions:
-
Format Phone Numbers Correctly
function formatPhoneNumber(phone) { // Remove all non-digits let cleaned = phone.replace(/\D/g, ''); // Remove leading + or 00 if present cleaned = cleaned.replace(/^(\+|00)/, ''); // Ensure country code is present if (!cleaned.startsWith('55') && cleaned.length === 11) { cleaned = '55' + cleaned; } return cleaned; } const formatted = formatPhoneNumber('+55 (11) 99999-9999'); // Result: 5511999999999 -
Validate Before Sending
REM BASIC validation SUB SEND VALIDATED WHATSAPP NUMBER$, MESSAGE$ LET CLEANED$ = "" FOR I = 1 TO LEN(NUMBER$) LET CH$ = MID$(NUMBER$, I, 1) IF CH$ >= "0" AND CH$ <= "9" THEN CLEANED$ = CLEANED$ + CH$ END IF NEXT I IF LEN(CLEANED$) < 10 OR LEN(CLEANED$) > 15 THEN LOG "Invalid phone number: " + NUMBER$ EXIT SUB END IF SEND WHATSAPP TO CLEANED$ WITH MESSAGE$ END SUB
Message Delivery Failures
Issue: Messages Not Delivered (24-Hour Window)
Symptoms:
- Messages work when user messages first
- Fail after 24 hours of inactivity
- “24-hour window” error
Diagnosis:
# Check last message timestamp
# Meta allows business-initiated messages only within 24 hours of last user message
Solutions:
-
Use Message Templates
In Meta Dashboard: 1. Go to WhatsApp > Message Templates 2. Create and submit template for approval 3. Use template for business-initiated messages -
Send Template Message
async function sendTemplate(to, templateName, parameters = []) { const response = await axios.post( `${baseURL}/${phoneNumberId}/messages`, { messaging_product: 'whatsapp', to: to, type: 'template', template: { name: templateName, language: { code: 'pt_BR' }, components: [{ type: 'body', parameters: parameters.map(p => ({ type: 'text', text: p })) }] } }, { headers: { 'Authorization': `Bearer ${accessToken}` } } ); return response.data; } -
Track Last Interaction
// Store last message time const lastInteraction = new Map(); async function sendMessage(to, message) { const lastTime = lastInteraction.get(to); const now = Date.now(); const hoursSince = (now - lastTime) / (1000 * 60 * 60); if (hoursSince > 24) { // Use template await sendTemplate(to, 'reengagement_template', []); } else { // Use regular message await sendText(to, message); } lastInteraction.set(to, now); }
Issue: Media Messages Fail to Send
Symptoms:
- Text messages work, media fails
- “Media upload failed” error
- Images not delivered
Diagnosis:
# Test media URL accessibility
curl -I https://your-media-url.com/image.jpg
# Expected: 200 OK with Content-Type: image/jpeg
Solutions:
-
Verify Media URL
- Must be publicly accessible
- Must use HTTPS
- Must return correct Content-Type
- Should be under 5MB for images
-
Upload Media First
async function uploadMedia(mediaUrl) { const response = await axios.post( `${baseURL}/${phoneNumberId}/media`, { file: mediaUrl, type: 'image/jpeg' }, { headers: { 'Authorization': `Bearer ${accessToken}` } } ); return response.data.id; } async function sendImageWithUpload(to, imageUrl, caption) { const mediaId = await uploadMedia(imageUrl); await sendMediaById(to, mediaId, caption); } -
Use Approved Media Hosts
- Meta’s media servers (preferred)
- AWS S3 with public access
- CloudFront CDN
- Avoid self-hosted media for reliability
Twilio-Specific Issues
Issue: TwiML Bin Not Working
Symptoms:
- TwiML Bin returns 404
- Invalid TwiML error
- Webhook not triggered
Diagnosis:
# Test TwiML Bin URL
curl -X POST https://handler.twilio.com/twiml/EH123...
Solutions:
-
Validate TwiML Syntax
<!-- Correct TwiML structure --> <?xml version="1.0" encoding="UTF-8"?> <Response> <Gather action="https://your-domain.com/gather" method="POST"> <Say voice="alice">Enter your code</Say> </Gather> </Response> -
Use Custom Webhook Instead
// Replace TwiML Bin with custom server app.post('/twilio/voice', (req, res) => { const twiml = new twilio.twiml.VoiceResponse(); const gather = twiml.gather({ action: '/twilio/gather', method: 'POST' }); gather.say('Enter your code'); res.type('text/xml'); res.send(twiml.toString()); });
Issue: Call Not Forwarded to Email
Symptoms:
- Voicemail not received
- Email not sent
- Transcription not working
Diagnosis:
# Check TwiML voicemail URL
https://twimlets.com/voicemail?Email=your-email@example.com
Solutions:
-
Implement Custom Voicemail
app.post('/twilio/gather', (req, res) => { const code = req.body.Digits; // Send email with code transporter.sendMail({ from: 'bot@example.com', to: 'your-email@example.com', subject: 'WhatsApp Verification Code', text: `Your code is: ${code}` }); // Also send via SMS as backup client.messages.create({ to: '+5511999999999', from: process.env.TWILIO_NUMBER, body: `Code: ${code}` }); res.type('text/xml'); res.send('<Response><Say>Code sent to email</Say></Response>'); }); -
Add Multiple Notification Channels
- Email (primary)
- SMS backup
- Database logging
- Webhook notification
Meta-Specific Issues
Issue: Phone Number Not Approved
Symptoms:
- Number shows “Not Verified”
- Cannot send messages
- “Number quality” error
Diagnosis:
# Check number status in Meta Dashboard
# WhatsApp Accounts > Phone Numbers > Select Number
Solutions:
-
Verify Number Quality
- Number must be from reputable provider
- Avoid VOIP numbers
- Use local numbers for target country
-
Complete Business Verification
In Meta Dashboard: 1. Go to Business Settings > Business Verification 2. Submit business documents 3. Wait for approval (7-14 days) -
Request Higher Limits
In Meta Dashboard: 1. Go to WhatsApp > API Settings 2. Request increased messaging limits 3. Provide business justification
Issue: Webhook Signature Verification Fails
Symptoms:
- All webhooks rejected
- “Invalid signature” errors
- Security check failures
Diagnosis:
# Check X-Hub-Signature-256 header
curl -X POST https://your-domain.com/webhooks/whatsapp \
-H "X-Hub-Signature-256: sha256=..." \
-d '{"test": true}'
Solutions:
-
Implement Signature Verification
const crypto = require('crypto'); function verifySignature(payload, signature, appSecret) { const expectedSignature = 'sha256=' + crypto.createHmac('sha256', appSecret) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); } app.post('/webhooks/whatsapp', (req, res) => { const signature = req.headers['x-hub-signature-256']; const payload = JSON.stringify(req.body); if (!verifySignature(payload, signature, APP_SECRET)) { return res.status(403).send('Invalid signature'); } // Process webhook processWebhook(req.body); res.status(200).send('OK'); }); -
Get App Secret
In Meta Dashboard: 1. Go to App Settings > Basic 2. Copy App Secret 3. Store securely in environment variable
Performance Issues
Issue: Slow Webhook Response
Symptoms:
- Webhooks timeout
- Meta shows “Webhook slow” warning
- Messages delayed
Diagnosis:
# Measure webhook response time
time curl -X POST https://your-domain.com/webhooks/whatsapp \
-H "Content-Type: application/json" \
-d '{"object":"whatsapp_business_account","entry":[]}'
Solutions:
-
Optimize Webhook Handler
app.post('/webhooks/whatsapp', (req, res) => { // Acknowledge immediately res.status(200).send('OK'); // Process asynchronously setImmediate(() => { processMessageAsync(req.body); }); }); async function processMessageAsync(data) { // Heavy processing here await handleMessage(data); } -
Use Worker Queues
const { Queue } = require('bull'); const webhookQueue = new Queue('webhook-processing', { redis: { host: 'localhost', port: 6379 } }); app.post('/webhooks/whatsapp', async (req, res) => { await webhookQueue.add('process', req.body); res.status(200).send('OK'); }); webhookQueue.process('process', async (job) => { await handleMessage(job.data); }); -
Monitor Response Times
const responseTime = require('response-time'); app.use(responseTime((req, res, time) => { console.log(`${req.method} ${req.path} ${time}ms`); }));
Issue: High Memory Usage
Symptoms:
- Bot crashes with out of memory
- Slow response times
- High server load
Diagnosis:
# Check memory usage
node --inspect app.js
# Open chrome://inspect in Chrome
# Monitor process
pm2 monit
Solutions:
-
Limit Conversation State
// Don't store unlimited conversation history const MAX_HISTORY = 50; const conversations = new Map(); function addToConversation(phone, message) { let history = conversations.get(phone) || []; history.push({ message, time: Date.now() }); if (history.length > MAX_HISTORY) { history = history.slice(-MAX_HISTORY); } conversations.set(phone, history); } -
Use Redis for State
const redis = require('redis'); const client = redis.createClient(); async function setState(phone, key, value) { await client.hset(`conversation:${phone}`, key, JSON.stringify(value)); } async function getState(phone, key) { const value = await client.hget(`conversation:${phone}`, key); return value ? JSON.parse(value) : null; } -
Implement Cleanup
// Clean up old conversations setInterval(() => { const now = Date.now(); const MAX_AGE = 24 * 60 * 60 * 1000; // 24 hours for (const [phone, data] of conversations.entries()) { if (now - data.lastActivity > MAX_AGE) { conversations.delete(phone); } } }, 60 * 60 * 1000); // Every hour
Issue: LLM Hallucination Loop
Symptoms:
- Bot sends repeated content endlessly
- Same token/phrase appears multiple times (e.g., “GBJ2KP GBJ2KP GBJ2KP…”)
- Stream never completes
- Logs show rapid repeated patterns
Diagnosis:
# Check logs for hallucination detection
grep -E "hallucination (detected|loop)" botserver.log
# Look for identical token repetition
grep "identical token repeated" botserver.log
Solutions:
-
Built-in Hallucination Detector General Bots includes automatic detection for LLM hallucination loops in the LLM layer:
- Works for all channels (Web, WhatsApp, Telegram, etc.)
- Detects identical tokens repeated 10+ times
- Detects patterns repeated 5+ times consecutively
- Detects patterns appearing 8+ times in recent 500 chars
- Automatically stops stream and sends accumulated content
-
Detection Thresholds
#![allow(unused)] fn main() { // Configured in botserver/src/llm/hallucination_detector.rs HallucinationConfig { min_text_length: 50, // Minimum text before detection pattern_lengths: [3,4,5,6,8,10,15,20], // Pattern sizes to check consecutive_threshold: 5, // Consecutive repetitions to trigger occurrence_threshold: 8, // Total occurrences in window to trigger recent_text_window: 500, // Window size for occurrence counting identical_token_threshold: 10, // Identical tokens to trigger } } -
Monitor Detection in Logs
# Look for hallucination warnings (works for all channels) grep -E "hallucination (detected|loop)" botserver.log # Example log output: # WARN LLM hallucation detected: identical token repeated 10 times: "GBJ2KP" # WARN LLM hallucination loop detected: pattern "XYZ123" # WARN WA hallucination detected: Some("XYZ123"), stopping stream -
Reduce Hallucination Risk
- Use clear, specific system prompts
- Set appropriate temperature (0.7-0.9 for chat)
- Limit max tokens in responses
- Use well-structured prompts with examples
-
Customize Detection (Advanced)
#![allow(unused)] fn main() { // Adjust thresholds in botserver/src/llm/hallucination_detector.rs let config = HallucinationConfig { identical_token_threshold: 8, // Lower for more aggressive detection consecutive_threshold: 4, // Fewer repetitions to trigger ..Default::default() }; let mut detector = HallucinationDetector::new(config); }
Security Issues
Issue: Access Token Exposed
Symptoms:
- Token found in logs
- Token in version control
- Unauthorized API usage
Solutions:
-
Use Environment Variables
# .env file (add to .gitignore) WHATSAPP_API_KEY=EAAQyour_api_key_here WHATSAPP_PHONE_NUMBER_ID=YOUR_PHONE_NUMBER_ID// config.js module.exports = { whatsapp: { apiKey: process.env.WHATSAPP_API_KEY, phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID } }; -
Secure Config File
# Set proper permissions chmod 600 .gbot/config.csv chown bot-user:bot-group .gbot/config.csv -
Rotate Tokens Regularly
In Meta Dashboard: 1. Go to WhatsApp > API Setup 2. Generate new temporary token 3. Update config.csv 4. Restart bot 5. Invalidate old token
Issue: Webhook Abuse
Symptoms:
- Excessive webhook calls
- Spam messages
- High server load
Solutions:
-
Rate Limit Webhooks
const rateLimit = require('express-rate-limit'); const webhookLimiter = rateLimit({ windowMs: 60 * 1000, // 1 minute max: 100, // 100 requests per minute keyGenerator: (req) => { return req.body.entry?.[0]?.changes?.[0]?.value?.messages?.[0]?.from || 'unknown'; } }); app.post('/webhooks/whatsapp', webhookLimiter, (req, res) => { // Process webhook }); -
Validate Payload
function validateWebhookPayload(data) { if (!data.object === 'whatsapp_business_account') { return false; } if (!data.entry || !Array.isArray(data.entry)) { return false; } return true; } app.post('/webhooks/whatsapp', (req, res) => { if (!validateWebhookPayload(req.body)) { return res.status(400).send('Invalid payload'); } // Process webhook }); -
Monitor for Anomalies
const messageCounts = new Map(); function checkForAbuse(phoneNumber) { const count = messageCounts.get(phoneNumber) || 0; messageCounts.set(phoneNumber, count + 1); if (count > 100) { console.warn(`Potential abuse from ${phoneNumber}`); // Block or throttle } }
Getting Help
If you’re still experiencing issues after following this guide:
-
Check Community Resources
-
Enable Debug Logging
REM Enable detailed logging SET DEBUG MODE TO "verbose" LOG ALL WEBHOOK REQUESTS LOG ALL API RESPONSES -
Collect Diagnostic Information
# Export logs tar -czf whatsapp-debug-$(date +%Y%m%d).tar.gz \ .gbot/logs/ \ /var/log/whatsapp-webhook/ # Include configuration (redact sensitive data) # Include error messages # Include timestamps -
Create Support Ticket
- Include diagnostic tarball
- Describe expected vs actual behavior
- List steps to reproduce
- Include error messages
For the latest troubleshooting information, see Webhook Configuration Guide or Code Examples.