WhatsApp Module

Send and receive WhatsApp messages via Meta Cloud API v21.0 or Twilio, with 24-hour messaging window management, template support, and privacy-compliant phone hashing.

Module Meta Cloud API v21.0 Twilio 24h Window

Supported Providers

ProviderAPI VersionBest ForPricing Model
Meta Cloud APIv21.0Direct integration, full control, custom webhooksPer-conversation (first 1,000 free/month)
TwilioMessaging APIQuick setup, multi-channel, existing Twilio usersPer-message pricing

24-Hour Messaging Window

WhatsApp enforces a 24-hour messaging window policy. You can only send free-form text messages to a user within 24 hours of their last inbound message to your business number. After the window closes, you must use pre-approved message templates.

24-Hour Window Flow
User sends message to your business number
         │
         ▼
┌─────────────────────────────────────────────┐
│  WhatsAppConversationModel updated           │
│  last_inbound_at = now()                     │
│  window_expires_at = now() + 24h             │
└─────────────────────────────────────────────┘
         │
         │  Within 24h: Send any text message (free-form)
         │  After 24h:  Must use approved template
         │
         ▼
┌─────────────────────────────────────────────┐
│  On send attempt:                            │
│  if (now() < window_expires_at)              │
│    → Send free-form text ✓                   │
│  else                                        │
│    → Reject OR fallback to template          │
└─────────────────────────────────────────────┘

The WhatsAppConversationModel tracks each conversation:

ColumnDescription
organization_idOwning organization
phone_hashSHA-256 hash of the phone number
last_inbound_atTimestamp of last message from user
window_expires_atWhen the 24h window closes
conversation_idMeta conversation ID (for billing tracking)

Configuration: Meta Cloud API

FieldTypeRequiredDescription
providerstringYesSet to "meta"
business_account_idstringYesWhatsApp Business Account ID from Meta Business Manager
access_tokenstringYesPermanent access token (System User token recommended)
phone_number_idstringYesPhone Number ID from WhatsApp Manager
app_secretstringYesApp Secret for webhook signature verification
verify_tokenstringYesCustom string for webhook URL verification handshake
JSON — Meta Configuration Example
{
  "provider": "meta",
  "business_account_id": "123456789012345",
  "access_token": "EAAxxxxxxxxxxxxxxxxxxxxxxxxx",
  "phone_number_id": "109876543210987",
  "app_secret": "abcdef1234567890abcdef1234567890",
  "verify_token": "my-custom-verify-token-2024"
}

Configuration: Twilio

FieldTypeRequiredDescription
providerstringYesSet to "twilio"
account_sidstringYesTwilio Account SID (starts with AC)
auth_tokenstringYesTwilio Auth Token (stored encrypted)
from_numberstringYesWhatsApp-enabled number in format whatsapp:+1234567890

Webhook Verification (Meta)

Meta requires webhook URL verification before it will send events. The process:

  1. In Meta Developer Dashboard, set your webhook URL to: https://your-domain.com/api/webhooks/whatsapp
  2. Set the Verify Token to match your module configuration's verify_token value
  3. Meta sends a GET request with hub.mode=subscribe, hub.verify_token, and hub.challenge
  4. Automation Hub validates the token and returns hub.challenge as the response
  5. Meta confirms the webhook is active and begins sending events
PHP — Webhook Verification Handler
// GET /api/webhooks/whatsapp (verification)
public function verify(Request $request): Response
{
    $mode = $request->query('hub_mode');
    $token = $request->query('hub_verify_token');
    $challenge = $request->query('hub_challenge');

    if ($mode === 'subscribe' && $token === $config['verify_token']) {
        return response($challenge, 200);
    }

    return response('Forbidden', 403);
}

// POST /api/webhooks/whatsapp (incoming messages)
// Signature validated via: hash_equals(
//     'sha256=' . hash_hmac('sha256', $rawBody, $appSecret),
//     $request->header('X-Hub-Signature-256')
// )

Node Types

action.whatsapp.send

Sends a free-form text message. Requires an open 24-hour window.

Config FieldTypeRequiredDescription
tostringYesRecipient phone number in E.164 format (e.g., +573001234567)
messagestringYesText message body (supports variables)
fallback_templatestringNoTemplate name to use if window is closed

action.whatsapp.send_template

Sends an approved message template. Works even outside the 24-hour window.

Config FieldTypeRequiredDescription
tostringYesRecipient phone number in E.164 format
template_namestringYesApproved template name (as registered in Meta)
languagestringYesTemplate language code (e.g., en_US, es)
variablesarrayNoTemplate variable values in order: ["{{1}}", "{{2}}"]
JSON — Template Node Configuration
{
  "type": "action.whatsapp.send_template",
  "config": {
    "to": "{{trigger.phone}}",
    "template_name": "order_confirmation",
    "language": "es",
    "variables": [
      "{{trigger.customer_name}}",
      "{{trigger.order_id}}",
      "{{trigger.delivery_date}}"
    ]
  }
}

Fallback Template Behavior

When using action.whatsapp.send with a fallback_template configured:

  1. The module checks if the 24-hour window is open for the recipient
  2. If open: sends the free-form text message directly
  3. If closed: automatically sends the specified template instead
  4. The execution step output indicates which path was taken (sent_as: "text" or sent_as: "template")

Best Practice

Always configure a fallback template for critical notifications (like order updates). This ensures the message is delivered even if the customer hasn't messaged you recently.

Phone Number Hashing (Privacy)

For compliance with Ley 1581 de 2012 (Colombian data protection law) and GDPR-equivalent regulations, the WhatsApp Module stores phone numbers as SHA-256 hashes in the conversation tracking table.

PHP — Phone Number Hashing
// Phone numbers are normalized and hashed before storage
$normalized = preg_replace('/[^0-9+]/', '', $phoneNumber);
$hash = hash('sha256', $normalized);

// Stored in whatsapp_conversations.phone_hash
// The plain phone number is NEVER stored in the database
// It only exists transiently during message send/receive

This means:

Common Issues

ProblemCauseSolution
Window closed error Attempting to send free-form text outside 24h window Use action.whatsapp.send_template or configure a fallback_template
Template not approved Template still pending review or rejected by Meta Check template status in Meta Business Manager. Use only APPROVED templates.
Invalid phone number Number not in E.164 format or not on WhatsApp Ensure format is +[country][number] with no spaces. Verify the number has WhatsApp.
Rate limited (Meta) Too many messages sent; tier limit reached Check your messaging tier in Meta Business Manager. Request tier upgrade if needed.
Webhook not receiving events Verification failed or wrong URL Verify the verify_token matches. Ensure HTTPS URL is publicly accessible.
Signature verification failed Wrong app_secret configured Copy the App Secret from Meta Developer Dashboard → App Settings → Basic.