Skip to main content

WhatsApp Webhooks

Agent-TS supports two webhook endpoints for receiving WhatsApp messages: one for Meta’s WhatsApp Business API and one for external clients like whatsapp-web.js.

Endpoints

External Client Webhook

POST /api/v1/external/whatsapp For external WhatsApp clients (whatsapp-web.js, Baileys, etc.).

Meta Business API Webhook

GET /api/v1/webhooks/whatsapp - Verification POST /api/v1/webhooks/whatsapp - Message handling For Meta’s official WhatsApp Business API.

External Client Integration

Text Message Payload

{
  "event": "message",
  "message": "What is my balance?",
  "from": "23279648205@c.us",
  "phoneE164": "+23279648205"
}

Image Message Payload

{
  "event": "message",
  "messageType": "image",
  "hasMedia": true,
  "phoneE164": "+23279372497",
  "message": "Here is my ID",
  "media": {
    "data": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
    "mimetype": "image/jpeg",
    "filename": "id_card_front.jpg",
    "size": 52643
  }
}
The media.data field must include the data URI prefix (data:image/jpeg;base64,).

Response Format

{
  "answer": "Your balance is 5,000 SLE",
  "status": "success"
}

whatsapp-web.js Integration

Example client implementation:
const { Client, LocalAuth } = require('whatsapp-web.js');
const axios = require('axios');

const client = new Client({
  authStrategy: new LocalAuth()
});

const AGENT_URL = 'http://localhost:8000/api/v1/external/whatsapp';

client.on('message', async (msg) => {
  // Skip group messages
  if (msg.from.includes('@g.us')) return;
  
  const payload = {
    event: 'message',
    from: msg.from,
    phoneE164: `+${msg.from.split('@')[0]}`,
    message: msg.body,
    messageType: msg.type,
    hasMedia: msg.hasMedia
  };
  
  // Handle media messages
  if (msg.hasMedia) {
    const media = await msg.downloadMedia();
    payload.media = {
      data: `data:${media.mimetype};base64,${media.data}`,
      mimetype: media.mimetype,
      filename: media.filename,
      size: Buffer.from(media.data, 'base64').length
    };
  }
  
  try {
    const response = await axios.post(AGENT_URL, payload);
    
    if (response.data.answer) {
      await msg.reply(response.data.answer);
    }
  } catch (error) {
    console.error('Agent error:', error);
    await msg.reply('Sorry, I encountered an error. Please try again.');
  }
});

client.initialize();

Meta WhatsApp Business API

Webhook Verification

Meta sends a GET request to verify your webhook:
GET /api/v1/webhooks/whatsapp?hub.mode=subscribe&hub.verify_token=YOUR_TOKEN&hub.challenge=CHALLENGE
The agent responds with the challenge if the verify token matches WHATSAPP_VERIFY_TOKEN.

Incoming Message Payload

{
  "object": "whatsapp_business_account",
  "entry": [{
    "id": "WHATSAPP_BUSINESS_ACCOUNT_ID",
    "changes": [{
      "value": {
        "messaging_product": "whatsapp",
        "metadata": {
          "display_phone_number": "15551234567",
          "phone_number_id": "PHONE_NUMBER_ID"
        },
        "contacts": [{
          "profile": { "name": "John Doe" },
          "wa_id": "23279648205"
        }],
        "messages": [{
          "from": "23279648205",
          "id": "wamid.xxx",
          "timestamp": "1234567890",
          "type": "text",
          "text": { "body": "What is my balance?" }
        }]
      }
    }]
  }]
}

Meta Developer Console Setup

  1. Go to Meta for Developers
  2. Create or select your app
  3. Add WhatsApp product
  4. Configure webhook URL: https://your-domain.com/api/v1/webhooks/whatsapp
  5. Set verify token: Same as WHATSAPP_VERIFY_TOKEN
  6. Subscribe to messages webhook field

Payload Fields Reference

FieldTypeRequiredDescription
eventstringYesEvent type (always “message”)
fromstringYesSender identifier
phoneE164stringYesPhone in E.164 format
messagestringNoText message content
messageTypestringNoMessage type (text, image, etc.)
hasMediabooleanNoWhether message has media
mediaobjectNoMedia data (if hasMedia)
media.datastringNoBase64 with data URI prefix
media.mimetypestringNoMIME type
media.filenamestringNoOriginal filename
media.sizenumberNoFile size in bytes

Error Responses

Validation Error

{
  "error": "Missing required field: phoneE164",
  "status": "error"
}

Agent Error

{
  "error": "Internal agent error",
  "status": "error"
}

Rate Limited

{
  "error": "Too many requests",
  "status": "error",
  "retryAfter": 60
}

Health Check

Verify the agent is running:
curl http://localhost:8000/health
Response:
{
  "status": "healthy",
  "service": "olive-agent",
  "version": "1.0.0",
  "activeConversations": 5
}

Testing

Test Text Message

curl -X POST http://localhost:8000/api/v1/external/whatsapp \
  -H "Content-Type: application/json" \
  -d '{
    "event": "message",
    "message": "what is my balance?",
    "from": "23279648205@c.us",
    "phoneE164": "+23279648205"
  }'

Test Transfer

curl -X POST http://localhost:8000/api/v1/external/whatsapp \
  -H "Content-Type: application/json" \
  -d '{
    "event": "message",
    "message": "send 5000 to CARD0001",
    "from": "23279648205@c.us",
    "phoneE164": "+23279648205"
  }'

Security Considerations

All incoming messages are sanitized to prevent injection attacks.
30 requests per minute per phone number by default.
Meta webhooks are verified using the configured verify token.
Production webhook endpoints must use HTTPS.

Next Steps