Skip to main content
Like AOF? Give us a star!
If you find AOF useful, please star us on GitHub. It helps us reach more developers and grow the community.

WhatsApp Integration Reference

Complete reference for AOF's WhatsApp Business Cloud API integration.

Configuration

DaemonConfig

apiVersion: aof.dev/v1
kind: DaemonConfig
metadata:
name: whatsapp-bot

spec:
server:
port: 3000
host: "0.0.0.0"

platforms:
whatsapp:
enabled: true
phone_number_id_env: "WHATSAPP_PHONE_NUMBER_ID"
access_token_env: "WHATSAPP_ACCESS_TOKEN"
verify_token_env: "WHATSAPP_VERIFY_TOKEN"
app_secret_env: "WHATSAPP_APP_SECRET"

# Optional: API version (default v18.0)
api_version: "v18.0"

# Optional: Phone number whitelist
allowed_numbers:
- "14155551234"
- "14155555678"

agents:
directory: "./agents"

runtime:
default_agent: "devops"

Environment Variables

VariableDescriptionRequired
WHATSAPP_PHONE_NUMBER_IDPhone Number ID from Meta BusinessYes
WHATSAPP_ACCESS_TOKENPermanent access tokenYes
WHATSAPP_VERIFY_TOKENCustom string for webhook verificationYes
WHATSAPP_APP_SECRETApp secret for signature verificationYes

WhatsAppConfig Fields

FieldTypeRequiredDefaultDescription
phone_number_idstringYes-Meta Phone Number ID
access_tokenstringYes-Graph API access token
verify_tokenstringYes-Webhook verification token
app_secretstringYes-HMAC signature secret
business_account_idstringNo-WhatsApp Business Account ID
allowed_numbersstring[]NoallPhone number whitelist
api_versionstringNov18.0Graph API version

Webhook Endpoints

Verification (GET)

Meta verifies webhook ownership via GET request:

GET /webhook/whatsapp?hub.mode=subscribe&hub.verify_token=YOUR_TOKEN&hub.challenge=CHALLENGE

AOF returns the challenge if token matches.

Messages (POST)

POST /webhook/whatsapp
Content-Type: application/json
X-Hub-Signature-256: sha256=<signature>

{
"object": "whatsapp_business_account",
"entry": [{
"id": "WHATSAPP_BUSINESS_ACCOUNT_ID",
"changes": [{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550000000",
"phone_number_id": "123456789"
},
"contacts": [{
"profile": { "name": "John Doe" },
"wa_id": "14155551234"
}],
"messages": [{
"from": "14155551234",
"id": "wamid.xxx",
"timestamp": "1234567890",
"type": "text",
"text": { "body": "show pods" }
}]
},
"field": "messages"
}]
}]
}

Message Types

Text Messages

Incoming text messages:

{
"type": "text",
"text": {
"body": "show pods in production"
}
}

Button Replies

When user taps a reply button:

{
"type": "interactive",
"interactive": {
"type": "button_reply",
"button_reply": {
"id": "view_logs",
"title": "View Logs"
}
}
}

Parsed as: button:view_logs

List Replies

When user selects from a list:

{
"type": "interactive",
"interactive": {
"type": "list_reply",
"list_reply": {
"id": "agent_k8s",
"title": "K8s Agent",
"description": "Kubernetes operations"
}
}
}

Parsed as: list:agent_k8s

Response Types

Text Response

TriggerResponse {
text: "Pods are healthy",
status: ResponseStatus::Success,
actions: vec![],
}

Sent as:

{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "14155551234",
"type": "text",
"text": {
"preview_url": false,
"body": "✅ Pods are healthy"
}
}

Interactive Buttons

When response has actions (max 3):

TriggerResponse {
text: "Found CrashLoopBackOff",
status: ResponseStatus::Warning,
actions: vec![
Action { id: "view_logs", label: "View Logs" },
Action { id: "describe", label: "Describe Pod" },
Action { id: "events", label: "Check Events" },
],
}

Sent as:

{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "14155551234",
"type": "interactive",
"interactive": {
"type": "button",
"body": {
"text": "⚠️ Found CrashLoopBackOff"
},
"action": {
"buttons": [
{ "type": "reply", "reply": { "id": "view_logs", "title": "View Logs" } },
{ "type": "reply", "reply": { "id": "describe", "title": "Describe Pod" } },
{ "type": "reply", "reply": { "id": "events", "title": "Check Events" } }
]
}
}
}

Interactive Lists

For larger selections, use list messages:

platform.send_interactive_list(
"14155551234",
"Select Agent", // header
"Choose an agent to use", // body
"View Agents", // button text
vec![
ListSection {
title: "DevOps".to_string(),
rows: vec![
ListRow { id: "k8s", title: "K8s Agent", description: Some("Kubernetes ops") },
ListRow { id: "docker", title: "Docker Agent", description: Some("Container ops") },
],
},
],
).await?;

API Methods

WhatsAppPlatform

impl WhatsAppPlatform {
/// Create new WhatsApp platform adapter
pub fn new(config: WhatsAppConfig) -> Result<Self, PlatformError>;

/// Verify webhook subscription (GET request handler)
pub fn verify_webhook(&self, mode: &str, token: &str, challenge: &str) -> Option<String>;

/// Send text message
pub async fn send_text_message(&self, to: &str, text: &str) -> Result<String, PlatformError>;

/// Send interactive buttons (max 3, 20 chars each)
pub async fn send_interactive_buttons(
&self,
to: &str,
body_text: &str,
buttons: Vec<(String, String)>, // (id, title)
) -> Result<String, PlatformError>;

/// Send interactive list
pub async fn send_interactive_list(
&self,
to: &str,
header: &str,
body_text: &str,
button_text: &str,
sections: Vec<ListSection>,
) -> Result<String, PlatformError>;
}

TriggerPlatform Implementation

#[async_trait]
impl TriggerPlatform for WhatsAppPlatform {
/// Parse incoming webhook
async fn parse_message(
&self,
raw: &[u8],
headers: &HashMap<String, String>,
) -> Result<TriggerMessage, PlatformError>;

/// Send response to user
async fn send_response(
&self,
channel: &str, // Phone number
response: TriggerResponse,
) -> Result<(), PlatformError>;

/// Verify HMAC-SHA256 signature
async fn verify_signature(&self, payload: &[u8], signature: &str) -> bool;

fn platform_name(&self) -> &'static str { "whatsapp" }
fn supports_threading(&self) -> bool { false }
fn supports_interactive(&self) -> bool { true }
fn supports_files(&self) -> bool { true }
}

Security

HMAC-SHA256 Verification

All webhooks include signature in X-Hub-Signature-256 header:

sha256=<hex_encoded_hmac>

Verification:

let mut mac = HmacSha256::new_from_slice(app_secret.as_bytes())?;
mac.update(payload);
let computed = hex::encode(mac.finalize().into_bytes());
computed == provided_signature

Phone Number Whitelist

Restrict access to specific phone numbers:

platforms:
whatsapp:
allowed_numbers:
- "14155551234" # Only this number can use the bot

Non-whitelisted numbers receive no response (silent fail for security).

WhatsApp Limits

LimitValue
Text message body4096 characters
Reply buttons3 maximum
Button title20 characters
List sections10 maximum
Items per section10 maximum
Item title24 characters
Item description72 characters
List button text20 characters

Error Handling

PlatformError Types

ErrorCause
InvalidSignatureHMAC verification failed or phone not allowed
ParseErrorMalformed webhook payload
UnsupportedMessageTypeNon-text/interactive message
ApiErrorGraph API request failed

WhatsApp API Errors

Common error codes from Meta:

CodeDescription
100Invalid parameter
131030Recipient not on WhatsApp
131047Re-engagement message required
131051Message type not supported
368Temporarily blocked

Trigger Configuration

Basic WhatsApp Trigger

apiVersion: aof.dev/v1alpha1
kind: Trigger
metadata:
name: whatsapp-ops
spec:
platform: whatsapp
webhook:
path: /webhook/whatsapp
routing:
default_agent: devops

Multi-Agent Routing

apiVersion: aof.dev/v1alpha1
kind: Trigger
metadata:
name: whatsapp-multi-agent
spec:
platform: whatsapp
webhook:
path: /webhook/whatsapp
routing:
rules:
- match:
text_contains: ["pod", "deployment", "kubectl"]
agent: k8s-ops
- match:
text_contains: ["docker", "container", "image"]
agent: docker-ops
default_agent: devops

Rate Limits

WhatsApp Business API has per-phone-number limits:

TierMessages/day
Unverified250
Verified (Tier 1)1,000
Verified (Tier 2)10,000
Verified (Tier 3)100,000
Verified (Tier 4)Unlimited

Meta Setup

Prerequisites

  1. Meta Business Account at business.facebook.com
  2. Meta Developer Account at developers.facebook.com
  3. WhatsApp Business App created in Developer Console
  4. Phone Number added to WhatsApp Business Account

Webhook Configuration

In Meta Developer Console:

  1. Go to WhatsAppConfiguration
  2. Set Webhook URL: https://your-domain.com/webhook/whatsapp
  3. Set Verify Token: Same as WHATSAPP_VERIFY_TOKEN
  4. Subscribe to messages field

Access Token

Generate permanent token:

  1. Go to WhatsAppAPI Setup
  2. Under Permanent token, click Generate
  3. Copy and save as WHATSAPP_ACCESS_TOKEN

Comparison: Test vs Production

AspectTest ModeProduction
Phone numbersLimited (5)Unlimited
VerificationNot requiredBusiness verification required
Template messagesNot requiredRequired for initiating
Rate limits250/dayTiered (Up to unlimited)
CostFreePer-conversation pricing

See Also