diff --git a/src/18-appendix-external-services/channels.md b/src/18-appendix-external-services/channels.md index e12e4984..7b53ac5d 100644 --- a/src/18-appendix-external-services/channels.md +++ b/src/18-appendix-external-services/channels.md @@ -1 +1,275 @@ # Channel Integrations + +This guide covers integrating messaging channels with General Bots, focusing on WhatsApp Business API integration using Twilio-purchased phone numbers. + +## Overview + +General Bots supports multiple messaging channels through a unified API. This section focuses on WhatsApp Business API, the most widely used business messaging platform globally. + +## Supported Channels + +| Channel | Status | Config Keys | +|---------|--------|-------------| +| WhatsApp | βœ… Production Ready | `whatsapp-api-key`, `whatsapp-phone-number-id` | +| Twilio SMS | βœ… Production Ready | `twilio-account-sid`, `twilio-auth-token` | +| Instagram | βœ… Production Ready | `instagram-access-token`, `instagram-page-id` | +| Microsoft Teams | βœ… Production Ready | `teams-app-id`, `teams-app-password` | + +## WhatsApp Business Integration + +The most popular channel for business messaging. Complete integration guide: [WhatsApp Quick Start](./whatsapp-quick-start.md) + +### Quick Setup (5 minutes) + +1. **Purchase a phone number from Twilio** + ```bash + # Twilio Console > Phone Numbers > Buy a Number + # Select: Voice capability (required for verification) + # Example: +553322980098 + ``` + +2. **Create Meta App with WhatsApp** + ```bash + # https://developers.facebook.com/apps/ + # Create App > Business > Add WhatsApp product + ``` + +3. **Configure credentials in `config.csv`** + ```csv + whatsapp-enabled,true + whatsapp-api-key,EAAQdlso6aM8BOwlhc3yM6bbJkGyibQPGJd87zFDHtfaFoJDJPohMl2c5nXs4yYuuHwoXJWx0rQKo0VXgTwThPYzqLEZArOZBhCWPBUpq7YlkEJXFAgB6ZAb3eoUzZAMgNZCZA1sg11rT2G8e1ZAgzpRVRffU4jmMChc7ybcyIwbtGOPKZAXKcNoMRfUwssoLhDWr + whatsapp-phone-number-id,1158433381968079 + whatsapp-business-account-id,390727550789228 + whatsapp-webhook-verify-token,4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY + whatsapp-application-id,323250907549153 + ``` + +### BASIC Keywords for WhatsApp + +```basic +REM Send a message +SEND WHATSAPP TO "+5511999999999" WITH "Hello from General Bots!" + +REM Handle incoming messages +ON WHATSAPP MESSAGE RECEIVED + LET SENDER$ = GET WHATSAPP SENDER NUMBER + LET MESSAGE$ = GET WHATSAPP MESSAGE BODY + + REM Echo message back + SEND WHATSAPP TO SENDER$ WITH "You said: " + MESSAGE$ +END ON +``` + +### Credential Reference + +| Credential | Format | Example | Purpose | +|------------|--------|---------|---------| +| Access Token | `EAAQ...` | `EAAQdlso6aM8BOwl...` | API authentication | +| Phone Number ID | 16 digits | `1158433381968079` | Message sending endpoint | +| WABA ID | 15 digits | `390727550789228` | Business account identifier | +| Verify Token | Custom string | `4qIogZadggQ.BEoMeci...` | Webhook security | +| Application ID | 15 digits | `323250907549153` | App identifier | + +### Phone Number Verification + +Twilio numbers require **voice call verification** (not SMS): + +1. **Configure Twilio webhook** to capture verification calls + ```xml + + + + + Please enter your verification code. + + + ``` + +2. **In Meta Business Suite**: Select "Phone Call" verification method +3. **Enter the 6-digit code** received via email +4. **Verification complete** - number ready for WhatsApp + +See: [Webhook Configuration Guide](./whatsapp-webhooks.md) + +## Advanced Configuration + +### Message Templates + +For business-initiated messages outside the 24-hour window: + +```javascript +// Send template message +POST https://graph.facebook.com/v18.0/1158433381968079/messages +{ + "messaging_product": "whatsapp", + "to": "5511999999999", + "type": "template", + "template": { + "name": "hello_world", + "language": { "code": "pt_BR" } + } +} +``` + +### Rate Limiting + +WhatsApp enforces rate limits per tier: + +| Tier | Messages/Day | Messages/Second | +|------|--------------|-----------------| +| Tier 1 | 1,000 | 1 | +| Tier 2 | 10,000 | 5 | +| Tier 3 | 100,000 | 50 | +| Tier 4 | Unlimited | 1,000 | + +Implement rate limiting in your bot: + +```basic +REM Simple rate limiting +LET LAST_SENT = 0 +SUB SEND WHATSAPP WITH LIMIT TO NUMBER$, MESSAGE$ + LET NOW = TIMER + IF NOW - LAST_SENT < 1 THEN + WAIT 1 - (NOW - LAST_SENT) + END IF + SEND WHATSAPP TO NUMBER$ WITH MESSAGE$ + LAST_SENT = TIMER +END SUB +``` + +### Webhook Security + +Always verify webhook signatures: + +```javascript +// Node.js signature verification +const crypto = require('crypto'); + +function verifySignature(payload, signature, appSecret) { + const expected = 'sha256=' + + crypto.createHmac('sha256', appSecret) + .update(payload) + .digest('hex'); + return crypto.timingSafeEqual( + Buffer.from(signature), + Buffer.from(expected) + ); +} +``` + +## Complete Documentation + +For detailed guides and examples: + +- **[WhatsApp Quick Start Guide](./whatsapp-quick-start.md)** - 30-minute setup walkthrough +- **[Webhook Configuration](./whatsapp-webhooks.md)** - Detailed webhook setup for Twilio and Meta +- **[Code Examples](./whatsapp-examples.md)** - Examples in BASIC, Node.js, and Python +- **[Troubleshooting Guide](./whatsapp-troubleshooting.md)** - Common issues and solutions +- **[Quick Reference](./whatsapp-quick-reference.md)** - Commands, configs, and snippets + +## Other Channels + +### Twilio SMS + +Simple SMS integration using Twilio: + +```csv +# config.csv +twilio-account-sid,ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +twilio-auth-token,your_auth_token_here +twilio-from-number,+15551234567 +``` + +```basic +REM Send SMS +SEND SMS TO "+5511999999999" WITH "Hello via SMS!" +``` + +### Instagram Direct Messages + +Connect Instagram messaging: + +```csv +# config.csv +instagram-access-token,EAAxxxx... +instagram-page-id,123456789012345 +``` + +```basic +REM Send Instagram DM +SEND INSTAGRAM TO "1234567890" WITH "Hello via Instagram!" +``` + +## Configuration Template + +Complete channel configuration example: + +```csv +# config.csv + +# WhatsApp Business (Primary channel) +whatsapp-enabled,true +whatsapp-api-key,EAAQdlso6aM8BOwlhc3yM6bbJkGyibQPGJd87zFDHtfaFoJDJPohMl2c5nXs4yYuuHwoXJWx0rQKo0VXgTwThPYzqLEZArOZBhCWPBUpq7YlkEJXFAgB6ZAb3eoUzZAMgNZCZA1sg11rT2G8e1ZAgzpRVRffU4jmMChc7ybcyIwbtGOPKZAXKcNoMRfUwssoLhDWr +whatsapp-phone-number-id,1158433381968079 +whatsapp-business-account-id,390727550789228 +whatsapp-webhook-verify-token,4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY + +# Twilio SMS (Backup channel) +twilio-enabled,false +twilio-account-sid,ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +twilio-auth-token,your_auth_token_here +twilio-from-number,+15551234567 + +# Instagram (Social channel) +instagram-enabled,false +instagram-access-token,EAAxxxx... +instagram-page-id,123456789012345 +``` + +## Troubleshooting + +### Common Issues + +**Issue: Phone number verification fails** +- **Solution**: Ensure "Phone Call" verification is selected (not SMS) +- **Solution**: Verify Twilio webhook is configured correctly +- See: [Troubleshooting Guide](./whatsapp-troubleshooting.md) + +**Issue: Messages not sending** +- **Solution**: Check access token validity +- **Solution**: Verify phone number format: `5511999999999` (no +, no spaces) +- **Solution**: Ensure webhook is subscribed to "messages" field + +**Issue: Rate limit errors** +- **Solution**: Implement rate limiting in your bot +- **Solution**: Use message queues for bulk sending +- See: [Code Examples](./whatsapp-examples.md) + +## Best Practices + +1. **Never hardcode credentials** - Always use `config.csv` +2. **Implement retry logic** - Handle API failures gracefully +3. **Monitor rate limits** - Respect platform limits +4. **Secure webhooks** - Verify all incoming requests +5. **Test thoroughly** - Use ngrok for local testing +6. **Log everything** - Track message delivery and errors +7. **Use templates** - Pre-approved templates for business-initiated messages +8. **Handle errors** - Provide user-friendly error messages + +## Support + +- **Documentation**: [Full guide](./whatsapp-quick-start.md) +- **Examples**: [Code samples](./whatsapp-examples.md) +- **Community**: [General Bots Discord](https://discord.gg/general-bots) +- **Meta Docs**: [WhatsApp Business API](https://developers.facebook.com/docs/whatsapp/) +- **Twilio Docs**: [Twilio WhatsApp](https://www.twilio.com/docs/whatsapp) + +## Next Steps + +1. Complete the [Quick Start Guide](./whatsapp-quick-start.md) +2. Set up webhooks using [Webhook Configuration](./whatsapp-webhooks.md) +3. Explore [Code Examples](./whatsapp-examples.md) for your use case +4. Configure monitoring and error handling +5. Test with your team before launching to users + +For configuration of other services (LLM providers, databases, etc.), see [Appendix B: External Services](./README.md). \ No newline at end of file diff --git a/src/18-appendix-external-services/whatsapp-examples.md b/src/18-appendix-external-services/whatsapp-examples.md new file mode 100644 index 00000000..261f51ed --- /dev/null +++ b/src/18-appendix-external-services/whatsapp-examples.md @@ -0,0 +1,1410 @@ +# Code Examples for WhatsApp Integration + +This page provides practical code examples for integrating WhatsApp Business API with General Bots, covering common use cases and implementations in multiple languages. + +## Table of Contents + +- [BASIC Examples](#basic-examples) +- [Node.js Examples](#nodejs-examples) +- [Python Examples](#python-examples) +- [Common Use Cases](#common-use-cases) +- [Advanced Scenarios](#advanced-scenarios) + +## BASIC Examples + +### Simple Message Sender + +```basic +REM Send a simple text message via WhatsApp +SEND WHATSAPP TO "+5511999999999" WITH "Hello from General Bots!" +``` + +### Interactive Bot Response + +```basic +REM Handle incoming WhatsApp messages +ON WHATSAPP MESSAGE RECEIVED + REM Get message details + LET SENDER$ = GET WHATSAPP SENDER NUMBER + LET MESSAGE$ = GET WHATSAPP MESSAGE BODY + LET MESSAGE_ID$ = GET WHATSAPP MESSAGE ID + + REM Log the incoming message + LOG "Received from " + SENDER$ + ": " + MESSAGE$ + + REM Process based on message content + IF INSTR(UCASE$(MESSAGE$), "HELP") > 0 THEN + SEND HELP MENU TO SENDER$ + ELSEIF INSTR(UCASE$(MESSAGE$), "STATUS") > 0 THEN + SEND STATUS UPDATE TO SENDER$ + ELSE + SEND DEFAULT RESPONSE TO SENDER$ + END IF +END ON + +SUB SEND HELP MENU TO NUMBER$ + LET MENU$ = "πŸ€– *Bot Menu*" + CHR$(10) + MENU$ = MENU$ + CHR$(10) + "1. πŸ“Š Status - Check system status" + MENU$ = MENU$ + CHR$(10) + "2. 🌐 Weather - Get weather info" + MENU$ = MENU$ + CHR$(10) + "3. πŸ“§ Contact - Get support contact" + MENU$ = MENU$ + CHR$(10) + CHR$(10) + "Reply with a number or keyword" + + SEND WHATSAPP TO NUMBER$ WITH MENU$ +END SUB + +SUB SEND STATUS UPDATE TO NUMBER$ + LET STATUS$ = "βœ… *System Status*" + CHR$(10) + STATUS$ = STATUS$ + CHR$(10) + "πŸ”Ή Bot: Online" + STATUS$ = STATUS$ + CHR$(10) + "πŸ”Ή Uptime: " + GET SYSTEM UPTIME$() + STATUS$ = STATUS$ + CHR$(10) + "πŸ”Ή Memory: " + GET MEMORY USAGE$() + + SEND WHATSAPP TO NUMBER$ WITH STATUS$ +END SUB + +SUB SEND DEFAULT RESPONSE TO NUMBER$ + LET RESPONSE$ = "πŸ‘‹ Hello! I'm your General Bot assistant." + RESPONSE$ = RESPONSE$ + CHR$(10) + CHR$(10) + "Type *help* to see available commands." + + SEND WHATSAPP TO NUMBER$ WITH RESPONSE$ +END SUB +``` + +### Message with Formatting + +```basic +REM Send message with rich text formatting +SEND WHATSAPP TO "+5511999999999" WITH "*Bold text* and _italics_ and ~strikethrough~" +``` + +### Send Location + +```basic +REM Send a location message +SEND WHATSAPP TO "+5511999999999" WITH LOCATION AT "-23.5505,-46.6333" NAMED "SΓ£o Paulo" WITH ADDRESS "SΓ£o Paulo, Brazil" +``` + +### Send Media (Image) + +```basic +REM Send an image from URL +SEND WHATSAPP TO "+5511999999999" WITH IMAGE FROM "https://example.com/image.jpg" AND CAPTION "Check this out!" +``` + +### Interactive Menu with Button Response + +```basic +REM Create an interactive menu +REM Note: Interactive templates must be pre-approved by Meta + +REM Send a list message +SEND WHATSAPP TO "+5511999999999" WITH LIST "Choose an option" WITH HEADER "Main Menu" AND ITEMS "Status,Help,Contact,About" +``` + +## Node.js Examples + +### Configuration Setup + +```javascript +// config.js +module.exports = { + whatsapp: { + apiKey: process.env.WHATSAPP_API_KEY || 'EAAQdlso6aM8BOwlhc3yM6bbJkGyibQPGJd87zFDHtfaFoJDJPohMl2c5nXs4yYuuHwoXJWx0rQKo0VXgTwThPYzqLEZArOZBhCWPBUpq7YlkEJXFAgB6ZAb3eoUzZAMgNZCZA1sg11rT2G8e1ZAgzpRVRffU4jmMChc7ybcyIwbtGOPKZAXKcNoMRfUwssoLhDWr', + phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID || '1158433381968079', + wabaId: process.env.WHATSAPP_WABA_ID || '390727550789228', + verifyToken: process.env.WHATSAPP_VERIFY_TOKEN || '4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY', + apiVersion: 'v18.0' + } +}; +``` + +### Send Text Message + +```javascript +// whatsapp-client.js +const axios = require('axios'); +const config = require('./config'); + +class WhatsAppClient { + constructor() { + this.baseURL = `https://graph.facebook.com/${config.whatsapp.apiVersion}`; + this.phoneNumberId = config.whatsapp.phoneNumberId; + this.accessToken = config.whatsapp.apiKey; + } + + async sendText(to, message) { + try { + const response = await axios.post( + `${this.baseURL}/${this.phoneNumberId}/messages`, + { + messaging_product: 'whatsapp', + to: to.replace(/[^\d]/g, ''), // Remove non-digits + type: 'text', + text: { + body: message + } + }, + { + headers: { + 'Authorization': `Bearer ${this.accessToken}`, + 'Content-Type': 'application/json' + } + } + ); + + return response.data; + } catch (error) { + console.error('Error sending message:', error.response?.data || error.message); + throw error; + } + } + + async sendFormattedText(to, message) { + // WhatsApp formatting: *bold*, _italics_, ~strikethrough~, ```monospace``` + return await this.sendText(to, message); + } +} + +module.exports = WhatsAppClient; +``` + +### Send Media Message + +```javascript +// Extend WhatsAppClient with media methods +class WhatsAppClientWithMedia extends WhatsAppClient { + async sendImage(to, imageUrl, caption = '') { + try { + const response = await axios.post( + `${this.baseURL}/${this.phoneNumberId}/messages`, + { + messaging_product: 'whatsapp', + to: to.replace(/[^\d]/g, ''), + type: 'image', + image: { + link: imageUrl, + caption: caption + } + }, + { + headers: { + 'Authorization': `Bearer ${this.accessToken}`, + 'Content-Type': 'application/json' + } + } + ); + + return response.data; + } catch (error) { + console.error('Error sending image:', error.response?.data || error.message); + throw error; + } + } + + async sendDocument(to, documentUrl, filename, caption = '') { + try { + const response = await axios.post( + `${this.baseURL}/${this.phoneNumberId}/messages`, + { + messaging_product: 'whatsapp', + to: to.replace(/[^\d]/g, ''), + type: 'document', + document: { + link: documentUrl, + filename: filename, + caption: caption + } + }, + { + headers: { + 'Authorization': `Bearer ${this.accessToken}`, + 'Content-Type': 'application/json' + } + } + ); + + return response.data; + } catch (error) { + console.error('Error sending document:', error.response?.data || error.message); + throw error; + } + } + + async sendLocation(to, latitude, longitude, name, address) { + try { + const response = await axios.post( + `${this.baseURL}/${this.phoneNumberId}/messages`, + { + messaging_product: 'whatsapp', + to: to.replace(/[^\d]/g, ''), + type: 'location', + location: { + latitude: latitude, + longitude: longitude, + name: name, + address: address + } + }, + { + headers: { + 'Authorization': `Bearer ${this.accessToken}`, + 'Content-Type': 'application/json' + } + } + ); + + return response.data; + } catch (error) { + console.error('Error sending location:', error.response?.data || error.message); + throw error; + } + } +} + +module.exports = WhatsAppClientWithMedia; +``` + +### Webhook Handler (Express) + +```javascript +// webhook-handler.js +const express = require('express'); +const WhatsAppClient = require('./whatsapp-client'); + +const app = express(); +app.use(express.json()); + +const whatsapp = new WhatsAppClient(); + +// Webhook verification (GET request) +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 { + console.log('❌ Webhook verification failed'); + res.sendStatus(403); + } +}); + +// Webhook message handler (POST request) +app.post('/webhooks/whatsapp', async (req, res) => { + try { + const data = req.body; + + if (data.object === 'whatsapp_business_account') { + for (const entry of data.entry) { + for (const change of entry.changes) { + if (change.field === 'messages') { + const message = change.value.messages[0]; + await handleMessage(message); + } + } + } + } + + res.status(200).send('OK'); + } catch (error) { + console.error('❌ Webhook error:', error); + res.status(500).send('Error'); + } +}); + +async function handleMessage(message) { + const from = message.from; + const body = message.text.body; + const messageId = message.id; + const timestamp = message.timestamp; + + console.log(`πŸ“© Message from ${from}: ${body}`); + + // Process the message + const response = await generateResponse(body); + + // Send reply + await whatsapp.sendText(from, response); + + // Mark as read (optional) + await markAsRead(messageId); +} + +async function generateResponse(userMessage) { + const lowerMessage = userMessage.toLowerCase(); + + if (lowerMessage.includes('help') || lowerMessage === '1') { + return `πŸ€– *Bot Menu* + +1. πŸ“Š Status - Check system status +2. 🌐 Weather - Get weather info +3. πŸ“§ Contact - Get support contact + +Reply with a number or keyword`; + } else if (lowerMessage.includes('status') || lowerMessage === '2') { + return `βœ… *System Status* + +πŸ”Ή Bot: Online +πŸ”Ή Uptime: ${process.uptime().toFixed(2)}s +πŸ”Ή Memory: ${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)}MB + +Type *help* for more options.`; + } else { + return `πŸ‘‹ Hello! I'm your General Bot assistant. + +Type *help* to see available commands. + +You said: ${userMessage}`; + } +} + +async function markAsRead(messageId) { + try { + await axios.post( + `https://graph.facebook.com/${config.whatsapp.apiVersion}/${messageId}`, + { + messaging_product: 'whatsapp', + status: 'read' + }, + { + headers: { + 'Authorization': `Bearer ${config.whatsapp.apiKey}`, + 'Content-Type': 'application/json' + } + } + ); + } catch (error) { + console.error('Error marking as read:', error.message); + } +} + +const PORT = process.env.PORT || 3000; +app.listen(PORT, () => { + console.log(`πŸš€ WhatsApp webhook server running on port ${PORT}`); +}); +``` + +### Interactive Bot with Conversation State + +```javascript +// conversation-bot.js +const WhatsAppClient = require('./whatsapp-client'); + +class ConversationBot { + constructor() { + this.whatsapp = new WhatsAppClient(); + this.conversations = new Map(); // Store conversation state per user + } + + async handleMessage(from, message) { + // Get or create conversation state + let state = this.conversations.get(from) || { + step: 'initial', + data: {} + }; + + // Process based on current step + const response = await this.processStep(state, message); + + // Update state + this.conversations.set(from, state); + + // Send response + await this.whatsapp.sendText(from, response); + } + + async processStep(state, message) { + const lowerMessage = message.toLowerCase(); + + switch (state.step) { + case 'initial': + if (lowerMessage.includes('order')) { + state.step = 'awaiting_product'; + return 'πŸ›’ *Order Process*\n\nWhat product would you like to order?'; + } else if (lowerMessage.includes('support')) { + state.step = 'awaiting_issue'; + return '🎫 *Support*\n\nPlease describe your issue:'; + } else { + return 'πŸ‘‹ Welcome!\n\nReply with:\nβ€’ "order" to place an order\nβ€’ "support" for help'; + } + + case 'awaiting_product': + state.data.product = message; + state.step = 'awaiting_quantity'; + return `πŸ“¦ Product: *${message}*\n\nHow many would you like?`; + + case 'awaiting_quantity': + const quantity = parseInt(message); + if (isNaN(quantity) || quantity <= 0) { + return '❌ Please enter a valid number.'; + } + state.data.quantity = quantity; + state.step = 'confirm_order'; + return `πŸ“‹ *Order Summary*\n\nProduct: ${state.data.product}\nQuantity: ${quantity}\n\nReply "confirm" to place order or "cancel" to start over.`; + + case 'confirm_order': + if (lowerMessage === 'confirm') { + // Process order + const orderId = await this.placeOrder(state.data); + state.step = 'initial'; + state.data = {}; + return `βœ… Order placed successfully!\n\nOrder ID: ${orderId}\n\nThank you for your business!`; + } else if (lowerMessage === 'cancel') { + state.step = 'initial'; + state.data = {}; + return '❌ Order cancelled.\n\nReply with "order" to start over.'; + } else { + return 'Please reply "confirm" or "cancel".'; + } + + case 'awaiting_issue': + state.data.issue = message; + state.step = 'confirm_ticket'; + return `πŸ“ *Issue Description*\n\n${message}\n\nReply "confirm" to submit ticket or "cancel" to discard.`; + + case 'confirm_ticket': + if (lowerMessage === 'confirm') { + const ticketId = await this.createTicket(state.data); + state.step = 'initial'; + state.data = {}; + return `βœ… Support ticket created!\n\nTicket ID: ${ticketId}\n\nOur team will review your issue shortly.`; + } else if (lowerMessage === 'cancel') { + state.step = 'initial'; + state.data = {}; + return '❌ Ticket cancelled.\n\nReply with "support" to start over.'; + } else { + return 'Please reply "confirm" or "cancel".'; + } + + default: + state.step = 'initial'; + return 'πŸ‘‹ Welcome!\n\nReply with:\nβ€’ "order" to place an order\nβ€’ "support" for help'; + } + } + + async placeOrder(data) { + // Implement order placement logic + return 'ORD-' + Date.now(); + } + + async createTicket(data) { + // Implement ticket creation logic + return 'TKT-' + Date.now(); + } +} + +module.exports = ConversationBot; +``` + +## Python Examples + +### Configuration Setup + +```python +# config.py +import os +from dataclasses import dataclass + +@dataclass +class WhatsAppConfig: + api_key: str = os.getenv('WHATSAPP_API_KEY', 'EAAQdlso6aM8BOwlhc3yM6bbJkGyibQPGJd87zFDHtfaFoJDJPohMl2c5nXs4yYuuHwoXJWx0rQKo0VXgTwThPYzqLEZArOZBhCWPBUpq7YlkEJXFAgB6ZAb3eoUzZAMgNZCZA1sg11rT2G8e1ZAgzpRVRffU4jmMChc7ybcyIwbtGOPKZAXKcNoMRfUwssoLhDWr') + phone_number_id: str = os.getenv('WHATSAPP_PHONE_NUMBER_ID', '1158433381968079') + waba_id: str = os.getenv('WHATSAPP_WABA_ID', '390727550789228') + verify_token: str = os.getenv('WHATSAPP_VERIFY_TOKEN', '4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY') + api_version: str = 'v18.0' + +config = WhatsAppConfig() +``` + +### WhatsApp Client + +```python +# whatsapp_client.py +import requests +from typing import Dict, Optional +from config import config + +class WhatsAppClient: + def __init__(self): + self.base_url = f"https://graph.facebook.com/{config.api_version}" + self.phone_number_id = config.phone_number_id + self.access_token = config.api_key + self.headers = { + 'Authorization': f'Bearer {self.access_token}', + 'Content-Type': 'application/json' + } + + def send_text(self, to: str, message: str) -> Dict: + """Send a text message via WhatsApp""" + url = f"{self.base_url}/{self.phone_number_id}/messages" + + # Clean phone number (remove non-digits) + to = ''.join(filter(str.isdigit, to)) + + payload = { + 'messaging_product': 'whatsapp', + 'to': to, + 'type': 'text', + 'text': { + 'body': message + } + } + + response = requests.post(url, json=payload, headers=self.headers) + response.raise_for_status() + return response.json() + + def send_image(self, to: str, image_url: str, caption: str = '') -> Dict: + """Send an image message""" + url = f"{self.base_url}/{self.phone_number_id}/messages" + + to = ''.join(filter(str.isdigit, to)) + + payload = { + 'messaging_product': 'whatsapp', + 'to': to, + 'type': 'image', + 'image': { + 'link': image_url, + 'caption': caption + } + } + + response = requests.post(url, json=payload, headers=self.headers) + response.raise_for_status() + return response.json() + + def send_location(self, to: str, latitude: float, longitude: float, + name: str, address: str) -> Dict: + """Send a location message""" + url = f"{self.base_url}/{self.phone_number_id}/messages" + + to = ''.join(filter(str.isdigit, to)) + + payload = { + 'messaging_product': 'whatsapp', + 'to': to, + 'type': 'location', + 'location': { + 'latitude': latitude, + 'longitude': longitude, + 'name': name, + 'address': address + } + } + + response = requests.post(url, json=payload, headers=self.headers) + response.raise_for_status() + return response.json() + + def mark_as_read(self, message_id: str) -> Dict: + """Mark a message as read""" + url = f"{self.base_url}/{message_id}" + + payload = { + 'messaging_product': 'whatsapp', + 'status': 'read' + } + + response = requests.post(url, json=payload, headers=self.headers) + response.raise_for_status() + return response.json() +``` + +### Flask Webhook Handler + +```python +# webhook_handler.py +from flask import Flask, request, jsonify +import logging +from whatsapp_client import WhatsAppClient +from config import config + +app = Flask(__name__) +whatsapp = WhatsAppClient() +logging.basicConfig(level=logging.INFO) + +@app.route('/webhooks/whatsapp', methods=['GET']) +def verify_webhook(): + """Verify webhook with Meta""" + mode = request.args.get('hub.mode') + token = request.args.get('hub.verify_token') + challenge = request.args.get('hub.challenge') + + VERIFY_TOKEN = config.verify_token + + if mode == 'subscribe' and token == VERIFY_TOKEN: + logging.info("βœ… Webhook verified") + return challenge, 200 + else: + logging.error("❌ Webhook verification failed") + return 'Forbidden', 403 + +@app.route('/webhooks/whatsapp', methods=['POST']) +def webhook_handler(): + """Handle incoming WhatsApp messages""" + 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] + handle_message(message) + + return 'OK', 200 + except Exception as e: + logging.error(f"❌ Webhook error: {e}") + return 'Error', 500 + +def handle_message(message): + """Process incoming message""" + from_number = message['from'] + body = message['text']['body'] + message_id = message['id'] + + logging.info(f"πŸ“© Message from {from_number}: {body}") + + # Generate response + response = generate_response(body) + + # Send reply + whatsapp.send_text(from_number, response) + + # Mark as read + whatsapp.mark_as_read(message_id) + +def generate_response(user_message: str) -> str: + """Generate bot response based on user input""" + lower_message = user_message.lower() + + if 'help' in lower_message or lower_message == '1': + return """πŸ€– *Bot Menu* + +1. πŸ“Š Status - Check system status +2. 🌐 Weather - Get weather info +3. πŸ“§ Contact - Get support contact + +Reply with a number or keyword""" + + elif 'status' in lower_message or lower_message == '2': + return f"""βœ… *System Status* + +πŸ”Ή Bot: Online +πŸ”Ή Uptime: Active +πŸ”Ή Version: 1.0.0 + +Type *help* for more options.""" + + else: + return f"""πŸ‘‹ Hello! I'm your General Bot assistant. + +Type *help* to see available commands. + +You said: {user_message}""" + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=3000, debug=True) +``` + +## Common Use Cases + +### 1. Order Confirmation Bot + +```basic +REM Order confirmation workflow +ON WHATSAPP MESSAGE RECEIVED + LET SENDER$ = GET WHATSAPP SENDER NUMBER + LET MESSAGE$ = GET WHATSAPP MESSAGE BODY + + IF LEFT$(MESSAGE$, 5) = "ORDER" THEN + REM Extract order ID (format: ORDER 12345) + LET ORDER_ID$ = MID$(MESSAGE$, 7) + + REM Fetch order details from database + LET ORDER_DETAILS$ = GET ORDER DETAILS ORDER_ID$ + + REM Send confirmation + SEND WHATSAPP TO SENDER$ WITH "βœ… Order " + ORDER_ID$ + " confirmed!" + CHR$(10) + ORDER_DETAILS$ + ELSE + SEND WHATSAPP TO SENDER$ WITH "Send 'ORDER ' to check your order status" + END IF +END ON +``` + +### 2. Weather Information Bot + +```javascript +// Node.js weather bot +async function handleWeatherRequest(location) { + try { + // Call weather API + const weatherData = await fetchWeatherData(location); + + // Format response + const response = `🌀️ *Weather in ${location}* + +Temperature: ${weatherData.main.temp}Β°C +Condition: ${weatherData.weather[0].description} +Humidity: ${weatherData.main.humidity}% +Wind: ${weatherData.wind.speed} m/s + +Have a great day!`; + + return response; + } catch (error) { + return '❌ Unable to fetch weather data. Please try again.'; + } +} + +// In webhook handler +if (lowerMessage.includes('weather')) { + const location = lowerMessage.replace('weather', '').trim() || 'SΓ£o Paulo'; + const response = await handleWeatherRequest(location); + await whatsapp.sendText(from, response); +} +``` + +### 3. Support Ticket System + +```python +# Python support ticket bot +class SupportBot: + def __init__(self): + self.whatsapp = WhatsAppClient() + self.tickets = {} + + def handle_support_request(self, from_number, issue): + # Create ticket + ticket_id = f"TKT-{int(time.time())}" + self.tickets[ticket_id] = { + 'from': from_number, + 'issue': issue, + 'status': 'open', + 'created_at': datetime.now() + } + + # Send confirmation + response = f"""🎫 *Support Ticket Created* + +Ticket ID: {ticket_id} +Status: Open +Issue: {issue} + +Our team will review your request shortly. +Reply with 'STATUS {ticket_id}' to check updates.""" + + self.whatsapp.send_text(from_number, response) + + def check_ticket_status(self, from_number, ticket_id): + if ticket_id in self.tickets: + ticket = self.tickets[ticket_id] + response = f"""πŸ“‹ *Ticket Status* + +ID: {ticket_id} +Status: {ticket['status'].title()} +Created: {ticket['created_at'].strftime('%Y-%m-%d %H:%M')} + +Issue: {ticket['issue']}""" + else: + response = f"❌ Ticket {ticket_id} not found." + + self.whatsapp.send_text(from_number, response) +``` + +### 4. Appointment Scheduling + +```basic +REM Appointment scheduling bot +ON WHATSAPP MESSAGE RECEIVED + LET SENDER$ = GET WHATSAPP SENDER NUMBER + LET MESSAGE$ = GET WHATSAPP MESSAGE BODY + + IF LEFT$(MESSAGE$, 5) = "BOOK " THEN + REM Parse date (format: BOOK 2024-01-15 14:00) + LET DATE_STR$ = MID$(MESSAGE$, 6) + + REM Check availability + IF CHECK AVAILABILITY DATE_STR$ THEN + REM Book appointment + LET CONFIRMATION$ = BOOK APPOINTMENT SENDER$, DATE_STR$ + + REM Send confirmation + SEND WHATSAPP TO SENDER$ WITH "πŸ“… Appointment Confirmed!" + CHR$(10) + CONFIRMATION$ + ELSE + SEND WHATSAPP TO SENDER$ WITH "❌ Date not available. Please choose another time." + END IF + END IF +END ON +``` + +### 5. Poll/Survey Bot + +```javascript +// Node.js survey bot +class SurveyBot { + constructor() { + this.surveys = {}; + this.responses = {}; + } + + async createSurvey(from, question, options) { + const surveyId = `SURV-${Date.now()}`; + + this.surveys[surveyId] = { + question, + options, + responses: [] + }; + + let message = `πŸ“Š *Survey*\n\n${question}\n\n`; + options.forEach((opt, i) => { + message += `${i + 1}. ${opt}\n`; + }); + message += '\nReply with the option number to vote.'; + + await this.whatsapp.sendText(from, message); + return surveyId; + } + + async handleResponse(from, surveyId, choice) { + if (this.surveys[surveyId]) { + const survey = this.surveys[surveyId]; + + if (choice >= 1 && choice <= survey.options.length) { + survey.responses.push({ + from, + choice, + timestamp: new Date() + }); + + const selectedOption = survey.options[choice - 1]; + await this.whatsapp.sendText(from, `βœ… You voted for: ${selectedOption}`); + } else { + await this.whatsapp.sendText(from, '❌ Invalid option. Please try again.'); + } + } + } +} +``` + +### 6. Broadcast Message Sender + +```python +# Python broadcast sender +class BroadcastSender: + def __init__(self): + self.whatsapp = WhatsAppClient() + + def send_broadcast(self, recipients, message): + """Send message to multiple recipients""" + success_count = 0 + failed_count = 0 + + for recipient in recipients: + try: + self.whatsapp.send_text(recipient, message) + success_count += 1 + time.sleep(1) # Rate limiting + except Exception as e: + logging.error(f"Failed to send to {recipient}: {e}") + failed_count += 1 + + return { + 'total': len(recipients), + 'success': success_count, + 'failed': failed_count + } + +# Usage +broadcast = BroadcastSender() +recipients = ['+5511999999999', '+5511888888888', '+5511777777777'] +message = "πŸ“’ *Important Announcement*\n\nThis is a broadcast message to all subscribers." + +result = broadcast.send_broadcast(recipients, message) +print(f"Sent: {result['success']}/{result['total']}") +``` + +### 7. File Download Bot + +```basic +REM File download and send bot +ON WHATSAPP MESSAGE RECEIVED + LET SENDER$ = GET WHATSAPP SENDER NUMBER + LET MESSAGE$ = GET WHATSAPP MESSAGE BODY + + IF LEFT$(MESSAGE$, 4) = "GET " THEN + REM Extract filename + LET FILENAME$ = MID$(MESSAGE$, 5) + + REM Check if file exists + IF FILE EXISTS("documents/" + FILENAME$) THEN + REM Get file URL + LET FILE_URL$ = "https://your-domain.com/files/" + FILENAME$ + + REM Send document + SEND WHATSAPP TO SENDER$ WITH DOCUMENT FROM FILE_URL$ NAMED FILENAME$ + ELSE + SEND WHATSAPP TO SENDER$ WITH "❌ File not found: " + FILENAME$ + END IF + END IF +END ON +``` + +### 8. E-commerce Product Catalog + +```javascript +// Product catalog bot +const products = [ + { id: 1, name: 'Wireless Headphones', price: 299.90, image: 'https://example.com/headphones.jpg' }, + { id: 2, name: 'Smart Watch', price: 499.90, image: 'https://example.com/watch.jpg' }, + { id: 3, name: 'Bluetooth Speaker', price: 199.90, image: 'https://example.com/speaker.jpg' } +]; + +async function showProductCatalog(to) { + let message = 'πŸ›οΈ *Product Catalog*\n\n'; + + products.forEach(product => { + message += `${product.id}. *${product.name}*\n`; + message += ` Price: R$ ${product.price.toFixed(2)}\n\n`; + }); + + message += 'Reply with product number to see details.'; + + await whatsapp.sendText(to, message); +} + +async function showProductDetails(to, productId) { + const product = products.find(p => p.id === productId); + + if (product) { + await whatsapp.sendImage(to, product.image, `πŸ“¦ *${product.name}*\n\nPrice: R$ ${product.price.toFixed(2)}\n\nReply 'BUY ${productId}' to purchase.`); + } else { + await whatsapp.sendText(to, '❌ Product not found.'); + } +} + +// In webhook handler +if (lowerMessage === 'catalog' || lowerMessage === 'products') { + await showProductCatalog(from); +} else if (lowerMessage.startsWith('product ')) { + const productId = parseInt(lowerMessage.split(' ')[1]); + await showProductDetails(from, productId); +} +``` + +### 9. Daily Digest/Notification Bot + +```python +# Daily digest scheduler +import schedule +import time + +class DigestBot: + def __init__(self): + self.whatsapp = WhatsAppClient() + self.subscribers = set() + + def subscribe(self, number): + self.subscribers.add(number) + return "βœ… Subscribed to daily digest!" + + def unsubscribe(self, number): + self.subscribers.discard(number) + return "❌ Unsubscribed from daily digest." + + def send_daily_digest(self): + digest = self.generate_digest() + + for subscriber in self.subscribers: + try: + self.whatsapp.send_text(subscriber, digest) + except Exception as e: + logging.error(f"Failed to send digest to {subscriber}: {e}") + + def generate_digest(self): + # Generate daily digest content + return f"""πŸ“° *Daily Digest* + +πŸ“… {datetime.now().strftime('%Y-%m-%d')} + +β€’ Weather: Sunny, 25Β°C +β€’ News: 5 new articles +β€’ Events: 2 upcoming + +Have a great day! 🌟""" + + def start_scheduler(self): + schedule.every().day.at("09:00").do(self.send_daily_digest) + + while True: + schedule.run_pending() + time.sleep(60) +``` + +### 10. Multi-language Support Bot + +```basic +REM Multi-language bot +ON WHATSAPP MESSAGE RECEIVED + LET SENDER$ = GET WHATSAPP SENDER NUMBER + LET MESSAGE$ = GET WHATSAPP MESSAGE BODY + + REM Get user's preferred language (default: English) + LET LANG$ = GET USER LANGUAGE SENDER$ + + REM Check for language change command + IF LEFT$(MESSAGE$, 3) = "SET " THEN + LET NEW_LANG$ = UPPER$(MID$(MESSAGE$, 5)) + + IF NEW_LANG$ = "EN" OR NEW_LANG$ = "ES" OR NEW_LANG$ = "PT" THEN + SET USER LANGUAGE SENDER$ TO NEW_LANG$ + SEND WHATSAPP TO SENDER$ WITH GET TEXT "language_set" IN NEW_LANG$ + ELSE + SEND WHATSAPP TO SENDER$ WITH "❌ Unsupported language. Use: EN, ES, or PT" + END IF + ELSE + REM Process message in user's language + LET RESPONSE$ = PROCESS MESSAGE MESSAGE$ IN LANG$ + SEND WHATSAPP TO SENDER$ WITH RESPONSE$ + END IF +END ON + +REM Language text retrieval +FUNCTION GET TEXT$ KEY$, LANG$ + IF LANG$ = "EN" THEN + IF KEY$ = "language_set" THEN RETURN "Language set to English βœ…" + IF KEY$ = "welcome" THEN RETURN "Welcome! How can I help you today?" + ELSEIF LANG$ = "ES" THEN + IF KEY$ = "language_set" THEN RETURN "Idioma establecido en EspaΓ±ol βœ…" + IF KEY$ = "welcome" THEN RETURN "Β‘Bienvenido! ΒΏCΓ³mo puedo ayudarte hoy?" + ELSEIF LANG$ = "PT" THEN + IF KEY$ = "language_set" THEN RETURN "Idioma definido para PortuguΓͺs βœ…" + IF KEY$ = "welcome" THEN RETURN "Bem-vindo! Como posso ajudΓ‘-lo hoje?" + END IF + + RETURN "Text not found" +END FUNCTION +``` + +## Advanced Scenarios + +### Conversation Flow Management + +```javascript +// Advanced conversation state machine +class ConversationFlow { + constructor() { + this.flows = { + 'sales': { + 'initial': async (from, input, state) => { + state.products = await getProducts(); + return this.formatProductList(state.products); + }, + 'selecting_product': async (from, input, state) => { + const product = state.products.find(p => p.id === parseInt(input)); + if (product) { + state.selectedProduct = product; + state.step = 'confirming_purchase'; + return `You selected: ${product.name}\nPrice: $${product.price}\n\nConfirm purchase? (yes/no)`; + } + return 'Invalid product. Please select a valid number.'; + }, + 'confirming_purchase': async (from, input, state) => { + if (input.toLowerCase() === 'yes') { + const order = await createOrder(from, state.selectedProduct); + state.step = 'completed'; + return `βœ… Order created!\nOrder ID: ${order.id}\n\nThank you for your purchase!`; + } else if (input.toLowerCase() === 'no') { + state.step = 'initial'; + return this.formatProductList(state.products); + } + return 'Please reply "yes" or "no".'; + } + } + }; + } + + async processFlow(flowName, from, input, state) { + const flow = this.flows[flowName]; + if (!flow) return 'Flow not found'; + + const handler = flow[state.step] || flow['initial']; + return await handler(from, input, state); + } + + formatProductList(products) { + let message = 'πŸ›οΈ *Products*\n\n'; + products.forEach((p, i) => { + message += `${i + 1}. ${p.name} - $${p.price}\n`; + }); + message += '\nReply with the product number to purchase.'; + return message; + } +} +``` + +### Interactive Buttons and Lists + +```python +# Interactive message templates (must be pre-approved in Meta) +class InteractiveMessages: + @staticmethod + def send_list_message(to, header, body, options): + """Send an interactive list message""" + sections = [{ + 'title': header, + 'rows': [{'id': str(i), 'title': opt, 'description': ''} + for i, opt in enumerate(options)] + }] + + payload = { + 'messaging_product': 'whatsapp', + 'to': to, + 'type': 'interactive', + 'interactive': { + 'type': 'list', + 'header': { + 'type': 'text', + 'text': header + }, + 'body': { + 'text': body + }, + 'action': { + 'button': 'Select', + 'sections': sections + } + } + } + + return send_interactive(payload) + + @staticmethod + def send_button_message(to, text, buttons): + """Send an interactive button message""" + payload = { + 'messaging_product': 'whatsapp', + 'to': to, + 'type': 'interactive', + 'interactive': { + 'type': 'button', + 'body': { + 'text': text + }, + 'action': { + 'buttons': [ + { + 'type': 'reply', + 'reply': {'id': f'btn_{i}', 'title': btn} + } for i, btn in enumerate(buttons) + ] + } + } + } + + return send_interactive(payload) +``` + +### Media Upload and Management + +```javascript +// Media management utilities +class MediaManager { + constructor(whatsappClient) { + this.whatsapp = whatsappClient; + this.mediaCache = new Map(); + } + + async uploadMedia(url, mediaType = 'image') { + // Check cache first + if (this.mediaCache.has(url)) { + return this.mediaCache.get(url); + } + + try { + // Upload to Meta servers + const response = await axios.post( + `${this.baseURL}/${this.phone_number_id}/media`, + { + file: url, + type: mediaType + }, + { + headers: { + 'Authorization': `Bearer ${this.accessToken}` + } + } + ); + + const mediaId = response.data.id; + this.mediaCache.set(url, mediaId); + + return mediaId; + } catch (error) { + console.error('Media upload failed:', error); + throw error; + } + } + + async sendCachedImage(to, imageUrl, caption = '') { + const mediaId = await this.uploadMedia(imageUrl); + + return await this.whatsapp.send({ + to, + type: 'image', + image: { + id: mediaId, + caption + } + }); + } +} +``` + +### Rate Limiting and Queue Management + +```python +# Rate limiter for WhatsApp API +from datetime import datetime, timedelta +import time + +class RateLimiter: + def __init__(self, max_requests=1000, time_window=60): + self.max_requests = max_requests + self.time_window = time_window + self.requests = [] + self.queue = [] + + def can_make_request(self): + now = datetime.now() + cutoff = now - timedelta(seconds=self.time_window) + + # Remove old requests + self.requests = [r for r in self.requests if r > cutoff] + + return len(self.requests) < self.max_requests + + def record_request(self): + self.requests.append(datetime.now()) + + async def send_with_limit(self, send_func, *args, **kwargs): + if not self.can_make_request(): + wait_time = self.time_window - (datetime.now() - self.requests[0]).total_seconds() + if wait_time > 0: + time.sleep(wait_time) + + self.record_request() + return await send_func(*args, **kwargs) + +# Usage +rate_limiter = RateLimiter(max_requests=50, time_window=60) + +async def send_message_safe(to, message): + await rate_limiter.send_with_limit(whatsapp.send_text, to, message) +``` + +## Testing Examples + +### Unit Tests for WhatsApp Client + +```javascript +// whatsapp-client.test.js +const WhatsAppClient = require('./whatsapp-client'); +const nock = require('nock'); + +describe('WhatsAppClient', () => { + let client; + + beforeEach(() => { + client = new WhatsAppClient(); + }); + + test('sendText should send message successfully', async () => { + nock('https://graph.facebook.com') + .post('/v18.0/1158433381968079/messages') + .reply(200, { + messaging_product: 'whatsapp', + contacts: [{ input: '5511999999999', wa_id: '5511999999999' }], + messages: [{ id: 'wamid.example' }] + }); + + const result = await client.sendText('+5511999999999', 'Test message'); + + expect(result.messaging_product).toBe('whatsapp'); + expect(result.messages[0].id).toBeDefined(); + }); + + test('sendText should handle errors', async () => { + nock('https://graph.facebook.com') + .post('/v18.0/1158433381968079/messages') + .reply(400, { + error: { + message: 'Invalid phone number', + type: 'WhatsAppApiError' + } + }); + + await expect( + client.sendText('invalid', 'Test message') + ).rejects.toThrow(); + }); +}); +``` + +### Integration Tests with Twilio + +```python +# twilio_integration_test.py +import pytest +from twilio_integration import TwilioWebhookHandler + +def test_voice_webhook_verification(): + handler = TwilioWebhookHandler() + + # Mock Twilio request + request_data = { + 'CallSid': 'CA123', + 'From': '+1234567890', + 'To': '+553322980098', + 'CallStatus': 'ringing' + } + + # Process webhook + response = handler.handle_voice_call(request_data) + + # Verify TwiML response + assert '' in response + assert ' + + + + Please enter your verification code followed by the pound sign. + + + + https://twimlets.com/voicemail?Email=your-email@example.com + + +``` + +### Simple Voicemail + +```xml + + + Please leave your message after the tone. + + Thank you for your message. + +``` + +## Diagnostic Commands + +### Test Connectivity + +```bash +# Test webhook endpoint +curl -X POST https://your-domain.com/webhooks/whatsapp \ + -H "Content-Type: application/json" \ + -d '{"test": true}' + +# Test Meta API +curl -X GET "https://graph.facebook.com/v18.0/1158433381968079" \ + -H "Authorization: Bearer EAAQdlso6aM8BOwl..." + +# Test Twilio webhook +curl -X POST https://your-domain.com/twilio/voice \ + -d "CallSid=CA123&From=+1234567890&To=+553322980098" + +# Check SSL certificate +openssl s_client -connect your-domain.com:443 +``` + +### Monitor Logs + +```bash +# General Bots logs +tail -f .gbot/logs/bot.log + +# Webhook server logs (PM2) +pm2 logs whatsapp-webhook + +# System logs +journalctl -u whatsapp-webhook -f + +# Twilio debugger +# https://console.twilio.com/us1/develop/monitor/debugger + +# Meta webhook status +# https://developers.facebook.com/apps/YOUR_APP_ID/webhooks/ +``` + +## Rate Limits + +### WhatsApp Business API + +| Tier | Messages/Day | Messages/Second | +|------|--------------|-----------------| +| Tier 1 | 1,000 | 1 | +| Tier 2 | 10,000 | 5 | +| Tier 3 | 100,000 | 50 | +| Tier 4 | Unlimited | 1,000 | + +### Rate Limiting Implementation + +```javascript +const rateLimiter = { + requests: [], + maxRequests: 50, + timeWindow: 60000, // 1 minute + + canMakeRequest() { + const now = Date.now(); + this.requests = this.requests.filter(t => now - t < this.timeWindow); + return this.requests.length < this.maxRequests; + }, + + recordRequest() { + this.requests.push(Date.now()); + } +}; +``` + +## Formatting Syntax + +### Text Formatting + +```basic +REM Bold text +*bold text* + +REM Italics +_italics_ + +REM Strikethrough +~strikethrough~ + +REM Monospace +```monospace``` + +REM Combined +*_bold and italic_* + +REM Line breaks +Line 1 +Line 2 +``` + +### Message Examples + +```basic +REM Formatted menu +SEND WHATSAPP TO "+5511999999999" WITH "πŸ€– *Bot Menu*" + CHR$(10) + CHR$(10) + "1. πŸ“Š Status" + CHR$(10) + "2. 🌐 Weather" + CHR$(10) + "3. πŸ“§ Contact" + +REM Address +SEND WHATSAPP TO "+5511999999999" WITH "πŸ“ *Address:*" + CHR$(10) + "123 Main St" + CHR$(10) + "SΓ£o Paulo, SP" + CHR$(10) + "Brazil" + +REM Code snippet +SEND WHATSAPP TO "+5511999999999" WITH "```bash" + CHR$(10) + "npm install" + CHR$(10) + "```" +``` + +## URLs + +### Meta Platforms + +``` +Meta for Developers: +https://developers.facebook.com/ + +Meta Business Suite: +https://business.facebook.com/ + +WhatsApp Business API: +https://developers.facebook.com/docs/whatsapp/ + +Webhook Configuration: +https://developers.facebook.com/apps/YOUR_APP_ID/webhooks/ + +Message Templates: +https://business.facebook.com/latest/wa/manage/message-templates/ +``` + +### Twilio Platforms + +``` +Twilio Console: +https://console.twilio.com/ + +Twilio Debugger: +https://console.twilio.com/us1/develop/monitor/debugger + +TwiML Bins: +https://console.twilio.com/us1/develop/twiml/bins + +Phone Numbers: +https://console.twilio.com/us1/develop/phone-numbers/manage/active +``` + +### General Bots + +``` +Documentation: +https://botbook.general-bots.com/ + +Community Discord: +https://discord.gg/general-bots + +GitHub Repository: +https://github.com/general-bots/general-bots +``` + +## Quick Troubleshooting + +### Issue: Verification Code Not Received + +```bash +# Check Twilio webhook is configured +twilio phone-numerals:info +553322980098 + +# Test webhook manually +curl -X POST https://your-domain.com/twilio/voice \ + -d "CallSid=CA123&From=+1234567890" + +# Verify voice capability is enabled +# In Twilio Console: Phone Numbers > Active Numbers > Your Number +# Check "Voice" is enabled +``` + +### Issue: Messages Not Sending + +```bash +# Verify access token +curl -X GET "https://graph.facebook.com/v18.0/me" \ + -H "Authorization: Bearer YOUR_TOKEN" + +# Check phone number ID +curl -X GET "https://graph.facebook.com/v18.0/1158433381968079" \ + -H "Authorization: Bearer YOUR_TOKEN" + +# Test message format +# Ensure phone number: 5511999999999 (no +, no spaces) +``` + +### Issue: Webhook Not Receiving Messages + +```bash +# Verify webhook subscription +curl -X GET "https://graph.facebook.com/v18.0/YOUR_APP_ID/subscriptions" \ + -H "Authorization: Bearer YOUR_TOKEN" + +# Test webhook endpoint +curl -X POST https://your-domain.com/webhooks/whatsapp \ + -H "Content-Type: application/json" \ + -d '{"object":"whatsapp_business_account","entry":[]}' + +# Check webhook is subscribed to "messages" field +# In Meta Dashboard: WhatsApp > API Setup > Webhook > Manage +``` + +## Code Snippets + +### Validate Phone Number + +```javascript +function validatePhoneNumber(phone) { + // Remove non-digits + const cleaned = phone.replace(/\D/g, ''); + + // Check length (10-15 digits) + if (cleaned.length < 10 || cleaned.length > 15) { + return false; + } + + // Check if all digits + if (!/^\d+$/.test(cleaned)) { + return false; + } + + return cleaned; +} +``` + +### Format WhatsApp Message + +```javascript +function formatMessage(template, variables) { + let message = template; + + for (const [key, value] of Object.entries(variables)) { + message = message.replace(new RegExp(`{{${key}}}`, 'g'), value); + } + + return message; +} + +// Usage +const template = 'Hello {{name}}, your order {{orderId}} is confirmed!'; +const variables = { name: 'John', orderId: '12345' }; +const message = formatMessage(template, variables); +// Result: "Hello John, your order 12345 is confirmed!" +``` + +### Retry Logic + +```javascript +async function sendWithRetry(whatsapp, to, message, maxRetries = 3) { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await whatsapp.sendText(to, message); + } catch (error) { + if (attempt === maxRetries) { + throw error; + } + + // Exponential backoff + const delay = Math.pow(2, attempt) * 1000; + await new Promise(resolve => setTimeout(resolve, delay)); + } + } +} +``` + +## Environment Checklist + +### Development + +```bash +# Node.js +npm install express twilio body-parser axios + +# Python +pip install flask requests twilio + +# BASIC +REM No installation required +``` + +### Production + +```bash +# Reverse proxy (nginx) +apt install nginx + +# Process manager (PM2) +npm install -g pm2 + +# SSL certificate (Let's Encrypt) +apt install certbot python3-certbot-nginx +``` + +## Meta Console URLs + +### Direct Access + +Replace with your IDs: + +``` +WhatsApp Settings: +https://business.facebook.com/latest/settings/whatsapp_account/?business_id=312254061496740&selected_asset_id=303621682831134&selected_asset_type=whatsapp-business-account + +Webhook Configuration: +https://developers.facebook.com/apps/323250907549153/webhooks/ + +Message Templates: +https://business.facebook.com/latest/wa/manage/message-templates/?waba_id=390727550789228 + +API Usage: +https://developers.facebook.com/apps/323250907549153/usage/ +``` + +--- + +**For detailed documentation:** See [README.md](./README.md) +**For troubleshooting:** See [troubleshooting.md](./troubleshooting.md) +**For code examples:** See [examples.md](./examples.md) +**For webhook setup:** See [webhooks.md](./webhooks.md) \ No newline at end of file diff --git a/src/18-appendix-external-services/whatsapp-quick-start.md b/src/18-appendix-external-services/whatsapp-quick-start.md new file mode 100644 index 00000000..f0852863 --- /dev/null +++ b/src/18-appendix-external-services/whatsapp-quick-start.md @@ -0,0 +1,298 @@ +# Quick Start Guide + +Get your WhatsApp Business bot up and running in 30 minutes with this streamlined setup guide. + +## Prerequisites Checklist + +- [ ] Twilio account with $10+ credit +- [ ] Meta for Developers account +- [ ] Meta Business Suite account +- [ ] Publicly accessible webhook URL (use ngrok for testing) +- [ ] Basic command line knowledge + +## 30-Minute Setup + +### Step 1: Buy Twilio Number (5 minutes) + +```bash +# Log into Twilio Console +# https://console.twilio.com/ + +# Navigate to: Phone Numbers > Buy a Number +# Select: Voice capability (required!) +# Purchase number +# Example: +553322980098 +``` + +**Tip:** Choose a number from your target country for easier verification. + +### Step 2: Create Meta App (5 minutes) + +```bash +# Go to Meta for Developers +# https://developers.facebook.com/apps/ + +# Click: Create App > Business type +# App name: "My WhatsApp Bot" +# Add product: WhatsApp +# Create WhatsApp Business Account (WABA) +``` + +**Save these values:** +``` +WABA ID: 390727550789228 +Application ID: 323250907549153 +Phone Number ID: (after verification) +``` + +### Step 3: Configure Twilio Webhook (5 minutes) + +**Option A: TwiML Bin (Fastest)** + +```xml + + + + + Please enter your verification code. + + +``` + +**Option B: ngrok + Node.js (Recommended)** + +```bash +# Install dependencies +npm install express twilio body-parser + +# Create server.js +``` + +```javascript +const express = require('express'); +const twilio = require('twilio'); +const app = express(); + +app.use(require('body-parser').urlencoded({ extended: false })); + +app.post('/twilio/voice', (req, res) => { + const twiml = new twilio.twiml.VoiceResponse(); + twiml.redirect('https://twimlets.com/voicemail?Email=your-email@example.com'); + res.type('text/xml'); + res.send(twiml.toString()); +}); + +app.listen(3000); +``` + +```bash +# Start ngrok +ngrok http 3000 + +# Update Twilio number webhook to: +# https://abc123.ngrok.io/twilio/voice +``` + +### Step 4: Verify Phone Number (5 minutes) + +```bash +# In Meta Business Suite: +# 1. WhatsApp Accounts > Add Phone Number +# 2. Enter: +553322980098 +# 3. Select: "Phone Call" (NOT SMS!) +# 4. Click: Verify + +# Meta will call your Twilio number +# Check your email for the verification code +# Enter code in Meta dashboard +``` + +**Critical:** Select "Phone Call" verification - Twilio numbers don't support SMS! + +### Step 5: Get API Credentials (3 minutes) + +```bash +# In Meta for Developers: +# 1. Your App > WhatsApp > API Setup +# 2. Click: "Temporary Access Token" +# 3. Copy token (starts with EAAQ...) +# 4. Note Phone Number ID from URL +``` + +**Required credentials:** +```csv +whatsapp-api-key,EAAQdlso6aM8BOwlhc3yM6bbJkGyibQPGJd87zFDHtfaFoJDJPohMl2c5nXs4yYuuHwoXJWx0rQKo0VXgTwThPYzqLEZArOZBhCWPBUpq7YlkEJXFAgB6ZAb3eoUzZAMgNZCZA1sg11rT2G8e1ZAgzpRVRffU4jmMChc7ybcyIwbtGOPKZAXKcNoMRfUwssoLhDWr +whatsapp-phone-number-id,1158433381968079 +whatsapp-business-account-id,390727550789228 +whatsapp-webhook-verify-token,4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY +whatsapp-application-id,323250907549153 +whatsapp-enabled,true +``` + +### Step 6: Configure Webhook (5 minutes) + +```bash +# Start your webhook server +node server.js + +# In Meta Developers: +# 1. WhatsApp > API Setup > Webhook > Edit +# 2. Webhook URL: https://your-domain.com/webhooks/whatsapp +# 3. Verify Token: 4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY +# 4. Click: Verify and Save +# 5. Subscribe to: messages +``` + +### Step 7: Configure General Bots (2 minutes) + +```bash +# Edit .gbot/config.csv +``` + +```csv +key,value +whatsapp-enabled,true +whatsapp-api-key,EAAQdlso6aM8BOwlhc3yM6bbJkGyibQPGJd87zFDHtfaFoJDJPohMl2c5nXs4yYuuHwoXJWx0rQKo0VXgTwThPYzqLEZArOZBhCWPBUpq7YlkEJXFAgB6ZAb3eoUzZAMgNZCZA1sg11rT2G8e1ZAgzpRVRffU4jmMChc7ybcyIwbtGOPKZAXKcNoMRfUwssoLhDWr +whatsapp-phone-number-id,1158433381968079 +whatsapp-business-account-id,390727550789228 +whatsapp-webhook-verify-token,4qIogZadggQ.BEoMeciXIdl_MlkV_1DTx8Z_i0bYPxtSJwKSbH0FKlY +whatsapp-application-id,323250907549153 +``` + +### Step 8: Test Your Bot (5 minutes) + +```bash +# Send test message via API +curl -X POST \ + 'https://graph.facebook.com/v18.0/1158433381968079/messages' \ + -H 'Authorization: Bearer EAAQdlso6aM8BOwl...' \ + -H 'Content-Type: application/json' \ + -d '{ + "messaging_product": "whatsapp", + "to": "5511999999999", + "type": "text", + "text": {"body": "Hello from General Bots!"} + }' + +# Or use BASIC +``` + +```basic +REM Test your WhatsApp integration +SEND WHATSAPP TO "+5511999999999" WITH "Hello from General Bots!" +``` + +## Your First WhatsApp Bot + +Create a simple echo bot: + +```basic +REM Simple WhatsApp Echo Bot +ON WHATSAPP MESSAGE RECEIVED + LET SENDER$ = GET WHATSAPP SENDER NUMBER + LET MESSAGE$ = GET WHATSAPP MESSAGE BODY + + LOG "Message from " + SENDER$ + ": " + MESSAGE$ + + REM Echo back with acknowledgment + SEND WHATSAPP TO SENDER$ WITH "You said: " + MESSAGE$ +END ON +``` + +## Common First-Time Mistakes + +❌ **Don't select SMS verification** - Use "Phone Call" +❌ **Don't hardcode tokens** - Use config.csv +❌ **Don't forget webhook subscriptions** - Subscribe to "messages" +❌ **Don't use + in phone numbers** - Format: 5511999999999 +❌ **Don't ignore rate limits** - Max 1000 messages/second + +## Next Steps + +1. **Create message templates** for business-initiated conversations +2. **Set up persistent storage** for conversation history +3. **Implement retry logic** for failed messages +4. **Add monitoring** for webhook health +5. **Review security best practices** + +## Need Help? + +- πŸ“– [Full Documentation](./README.md) +- πŸ”§ [Troubleshooting Guide](./troubleshooting.md) +- πŸ’» [Code Examples](./examples.md) +- 🌐 [Webhook Configuration](./webhooks.md) +- πŸ’¬ [Community Discord](https://discord.gg/general-bots) + +## Verification Checklist + +- [ ] Twilio number purchased with Voice capability +- [ ] Meta app created with WhatsApp product +- [ ] Phone number verified via phone call +- [ ] Access token generated and saved +- [ ] Webhook configured and verified +- [ ] Webhook subscribed to "messages" +- [ ] config.csv updated with all credentials +- [ ] Test message sent successfully +- [ ] Incoming webhook received +- [ ] Bot replied to test message + +βœ… **All checked? Your WhatsApp bot is live!** + +## Quick Reference: Essential Commands + +```bash +# Test webhook connectivity +curl -X POST https://your-webhook.com/webhooks/whatsapp \ + -H "Content-Type: application/json" \ + -d '{"test":true}' + +# Check Meta API status +curl https://developers.facebook.com/status/ + +# View Twilio call logs +# https://console.twilio.com/us1/develop/monitor/logs/calls + +# Test access token +curl -X GET "https://graph.facebook.com/v18.0/me" \ + -H "Authorization: Bearer YOUR_TOKEN" + +# Monitor bot logs +tail -f .gbot/logs/bot.log +``` + +## Configuration Template + +Copy this template and replace with your values: + +```csv +# WhatsApp Business Configuration +whatsapp-enabled,true +whatsapp-api-key,YOUR_ACCESS_TOKEN_HERE +whatsapp-phone-number-id,YOUR_PHONE_NUMBER_ID_HERE +whatsapp-business-account-id,YOUR_WABA_ID_HERE +whatsapp-webhook-verify-token,YOUR_VERIFY_TOKEN_HERE +whatsapp-application-id,YOUR_APP_ID_HERE +whatsapp-from-number,+553322980098 + +# Optional: Advanced Settings +whatsapp-webhook-url,https://your-domain.com/webhooks/whatsapp +whatsapp-timeout,30000 +whatsapp-retry-attempts,3 +whatsapp-rate-limit,50 +``` + +## Time-Saving Tips + +πŸ’‘ **Use ngrok for testing** - No need to deploy to test webhooks +πŸ’‘ **Save all credentials immediately** - Tokens won't be shown again +πŸ’‘ **Test with your own number first** - Verify everything works +πŸ’‘ **Enable debug logging** - Troubleshoot issues faster +πŸ’‘ **Set up monitoring early** - Catch problems before users do + +--- + +**Estimated total time:** 30 minutes +**Difficulty:** Intermediate +**Cost:** ~$10/month (Twilio number + usage) + +For detailed explanations, advanced configurations, and production deployment, see the [complete documentation](./README.md). \ No newline at end of file diff --git a/src/18-appendix-external-services/whatsapp-troubleshooting.md b/src/18-appendix-external-services/whatsapp-troubleshooting.md new file mode 100644 index 00000000..177e7fe9 --- /dev/null +++ b/src/18-appendix-external-services/whatsapp-troubleshooting.md @@ -0,0 +1,1044 @@ +# Troubleshooting Guide + +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](#diagnostic-tools) +- [Verification Issues](#verification-issues) +- [Webhook Problems](#webhook-problems) +- [API Errors](#api-errors) +- [Message Delivery Failures](#message-delivery-failures) +- [Twilio-Specific Issues](#twilio-specific-issues) +- [Meta-Specific Issues](#meta-specific-issues) +- [Performance Issues](#performance-issues) +- [Security Issues](#security-issues) + +## Diagnostic Tools + +### Essential Commands + +```bash +# 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/1158433381968079" \ + -H "Authorization: Bearer EAAQdlso6aM8BOwl..." +``` + +### Log Locations + +```bash +# 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:** + +```bash +# 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:** + +1. **Verify Voice Webhook Configuration** + ```bash + # 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 + ``` + +2. **Verify Verification Method** + - Ensure "Phone Call" is selected (not SMS) + - Twilio numbers don't support SMS for verification + - Meta must call your number + +3. **Check TwiML Response** + ```xml + + + + + Please enter your verification code. + + + ``` + +4. **Test Webhook Locally** + ```bash + # 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:** + +```bash +# 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:** + +1. **Implement Email Forwarding** + ```javascript + // 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('Thank you'); + }); + ``` + +2. **Use Voicemail Fallback** + ```xml + + https://twimlets.com/voicemail?Email=your-email@example.com&Transcribe=true + + ``` + +3. **Add Logging** + ```basic + 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:** + +```bash +# 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:** + +1. **Match Verify Token Exactly** + ```javascript + // 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); + } + }); + ``` + +2. **Return Challenge Correctly** + - Must return the challenge as plain text + - Must respond with 200 OK status + - Must not include HTML or JSON formatting + +3. **Check URL Accessibility** + ```bash + # 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:** + +```bash +# 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 EAAQdlso6aM8BOwl..." +``` + +**Solutions:** + +1. **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 + ``` + +2. **Check Webhook URL** + ```javascript + // 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'); + }); + ``` + +3. **Verify Response Time** + - Webhook must respond within 3 seconds + - Process heavy operations asynchronously + - Return 200 OK immediately + + ```javascript + 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:** + +```bash +# 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:** + +1. **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 + ``` + +2. **Check Token Format** + ```csv + # config.csv + # Token must start with EAAQ... + whatsapp-api-key,EAAQdlso6aM8BOwlhc3yM6bbJkGyibQPGJd87zFDHtfaFoJDJPohMl2c5nXs4yYuuHwoXJWx0rQKo0VXgTwThPYzqLEZArOZBhCWPBUpq7YlkEJXFAgB6ZAb3eoUzZAMgNZCZA1sg11rT2G8e1ZAgzpRVRffU4jmMChc7ybcyIwbtGOPKZAXKcNoMRfUwssoLhDWr + ``` + +3. **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:** + +```bash +# Check API rate limits +# WhatsApp Business API: 1000 messages/second per WABA + +# Monitor message queue +tail -f .gbot/logs/message-queue.log +``` + +**Solutions:** + +1. **Implement Rate Limiting** + ```javascript + 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); + ``` + +2. **Use Message Queuing** + ```basic + REM BASIC message queuing + SUB SEND WHATSAPP WITH RATE LIMIT TO NUMBER$, MESSAGE$ + ADD TO QUEUE "whatsapp-outbound", NUMBER$ + "|" + MESSAGE$ + END SUB + ``` + +3. **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:** + +```bash +# Test phone number format +# Correct format: 5511999999999 (no +, no spaces, no dashes) +``` + +**Solutions:** + +1. **Format Phone Numbers Correctly** + ```javascript + 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 + ``` + +2. **Validate Before Sending** + ```basic + 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:** + +```bash +# Check last message timestamp +# Meta allows business-initiated messages only within 24 hours of last user message +``` + +**Solutions:** + +1. **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 + ``` + +2. **Send Template Message** + ```javascript + 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; + } + ``` + +3. **Track Last Interaction** + ```javascript + // 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:** + +```bash +# Test media URL accessibility +curl -I https://your-media-url.com/image.jpg + +# Expected: 200 OK with Content-Type: image/jpeg +``` + +**Solutions:** + +1. **Verify Media URL** + - Must be publicly accessible + - Must use HTTPS + - Must return correct Content-Type + - Should be under 5MB for images + +2. **Upload Media First** + ```javascript + 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); + } + ``` + +3. **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:** + +```bash +# Test TwiML Bin URL +curl -X POST https://handler.twilio.com/twiml/EH123... +``` + +**Solutions:** + +1. **Validate TwiML Syntax** + ```xml + + + + + Enter your code + + + ``` + +2. **Use Custom Webhook Instead** + ```javascript + // 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:** + +```bash +# Check TwiML voicemail URL +https://twimlets.com/voicemail?Email=your-email@example.com +``` + +**Solutions:** + +1. **Implement Custom Voicemail** + ```javascript + 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('Code sent to email'); + }); + ``` + +2. **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:** + +```bash +# Check number status in Meta Dashboard +# WhatsApp Accounts > Phone Numbers > Select Number +``` + +**Solutions:** + +1. **Verify Number Quality** + - Number must be from reputable provider + - Avoid VOIP numbers + - Use local numbers for target country + +2. **Complete Business Verification** + ``` + In Meta Dashboard: + 1. Go to Business Settings > Business Verification + 2. Submit business documents + 3. Wait for approval (7-14 days) + ``` + +3. **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:** + +```bash +# 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:** + +1. **Implement Signature Verification** + ```javascript + 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'); + }); + ``` + +2. **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:** + +```bash +# 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:** + +1. **Optimize Webhook Handler** + ```javascript + 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); + } + ``` + +2. **Use Worker Queues** + ```javascript + 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); + }); + ``` + +3. **Monitor Response Times** + ```javascript + 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:** + +```bash +# Check memory usage +node --inspect app.js +# Open chrome://inspect in Chrome + +# Monitor process +pm2 monit +``` + +**Solutions:** + +1. **Limit Conversation State** + ```javascript + // 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); + } + ``` + +2. **Use Redis for State** + ```javascript + 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; + } + ``` + +3. **Implement Cleanup** + ```javascript + // 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 + ``` + +## Security Issues + +### Issue: Access Token Exposed + +**Symptoms:** +- Token found in logs +- Token in version control +- Unauthorized API usage + +**Solutions:** + +1. **Use Environment Variables** + ```bash + # .env file (add to .gitignore) + WHATSAPP_API_KEY=EAAQdlso6aM8BOwl... + WHATSAPP_PHONE_NUMBER_ID=1158433381968079 + ``` + + ```javascript + // config.js + module.exports = { + whatsapp: { + apiKey: process.env.WHATSAPP_API_KEY, + phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID + } + }; + ``` + +2. **Secure Config File** + ```bash + # Set proper permissions + chmod 600 .gbot/config.csv + chown bot-user:bot-group .gbot/config.csv + ``` + +3. **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:** + +1. **Rate Limit Webhooks** + ```javascript + 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 + }); + ``` + +2. **Validate Payload** + ```javascript + 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 + }); + ``` + +3. **Monitor for Anomalies** + ```javascript + 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: + +1. **Check Community Resources** + - [General Bots Discord](https://discord.gg/general-bots) + - [Meta for Developers Forum](https://developers.facebook.com/community/) + - [Twilio Community](https://www.twilio.com/help/faq) + +2. **Enable Debug Logging** + ```basic + REM Enable detailed logging + SET DEBUG MODE TO "verbose" + LOG ALL WEBHOOK REQUESTS + LOG ALL API RESPONSES + ``` + +3. **Collect Diagnostic Information** + ```bash + # 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 + ``` + +4. **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](./webhooks.md) or [Code Examples](./examples.md). \ No newline at end of file diff --git a/src/18-appendix-external-services/whatsapp-webhooks.md b/src/18-appendix-external-services/whatsapp-webhooks.md new file mode 100644 index 00000000..9891cf8c --- /dev/null +++ b/src/18-appendix-external-services/whatsapp-webhooks.md @@ -0,0 +1,632 @@ +# 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). \ No newline at end of file diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 2c5b17ef..ba2db8b3 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -433,6 +433,11 @@ - [LLM Providers](./18-appendix-external-services/llm-providers.md) - [Weather API](./18-appendix-external-services/weather.md) - [Channel Integrations](./18-appendix-external-services/channels.md) + - [Quick Start Guide](./18-appendix-external-services/whatsapp-quick-start.md) + - [Webhook Configuration](./18-appendix-external-services/whatsapp-webhooks.md) + - [Code Examples](./18-appendix-external-services/whatsapp-examples.md) + - [Troubleshooting](./18-appendix-external-services/whatsapp-troubleshooting.md) + - [Quick Reference](./18-appendix-external-services/whatsapp-quick-reference.md) - [Storage Services](./18-appendix-external-services/storage.md) - [Directory Services](./18-appendix-external-services/directory.md) - [Attendance Queue](./18-appendix-external-services/attendance-queue.md) @@ -452,5 +457,7 @@ - [LXC Migration](./19-maintenance/lxc-migration.md) + + [Glossary](./glossary.md) [Contact](./contact/README.md)