Skip to Content
ProvidersWhatsApp Business API (Meta)

WhatsApp Business API (Meta)

Category: Notification — Messaging
Package: @leadmetrics/provider-whatsappWhatsAppBusinessProvider
External SDK: axios (Meta Graph API v19.0)


Purpose

WhatsApp Business API enables template-based WhatsApp messages to clients and staff. The platform uses WhatsApp for high-priority notifications — HITL review requests, monthly report delivery, and budget alerts — where WhatsApp achieves much higher open rates than email or SMS.

Both the platform and tenants use the same Meta WhatsApp Business API. The platform has its own WhatsApp Business number for default notifications; tenants can configure their own verified business number so messages arrive from their brand identity.


Key Constraint: Template-Only Messaging

WhatsApp Business API does not allow arbitrary free-text messages. All outbound messages must use pre-approved message templates. Templates must be submitted to Meta for approval before use. The platform maintains a catalogue of approved templates; tenants using their own account must register the same templates with Meta.


Config Structure

Platform default (env vars)

WHATSAPP_API_KEY=EAAxxxxxxxx # Meta Graph API access token WHATSAPP_API_BASE_URL=https://graph.facebook.com/v19.0 WHATSAPP_FROM=123456789012345 # Platform's verified WhatsApp Business phone number ID

Tenant config (stored in notification_providers.config, encrypted)

interface WhatsAppBusinessConfig { apiKey: string; // Meta Graph API access token (permanent token from System User) baseUrl: string; // "https://graph.facebook.com/v19.0" phoneNumberId: string; // Phone Number ID from Meta Business Manager (not the phone number itself) }

How to get phoneNumberId

In Meta Business Manager → WhatsApp → Phone Numbers → select number → copy the “Phone Number ID” (a 15-digit numeric string). This is not the human-readable phone number.


Templates

Platform-registered templates

Template nameCategoryVariables
hitl_review_requestUTILITY{{1}} = activity title, {{2}} = review URL
monthly_report_readyUTILITY{{1}} = client name, {{2}} = month, {{3}} = report URL
budget_warningUTILITY{{1}} = credits remaining, {{2}} = percent
credit_topup_confirmedUTILITY{{1}} = credits added, {{2}} = new balance
welcome_onboardingUTILITY{{1}} = user name, {{2}} = portal URL

All templates use the UTILITY category (transactional). Marketing templates require separate approval and cannot be sent without user opt-in.


Integration Pattern

Provider class (packages/provider-whatsapp/src/providers/whatsapp-business.ts)

import axios from 'axios'; class WhatsAppBusinessProvider implements WhatsAppProvider { readonly name = 'whatsapp_business_api'; constructor( private apiKey: string, private baseUrl: string, private phoneNumberId: string, ) {} async sendTemplate(message: WhatsAppMessage): Promise<WhatsAppSendResult> { const url = `${this.baseUrl}/${this.phoneNumberId}/messages`; const body = { messaging_product: 'whatsapp', to: message.to, // E.164: "+919876543210" type: 'template', template: { name: message.template.name, language: { code: message.template.languageCode ?? 'en' }, components: message.template.components?.map(comp => ({ type: comp.type, // 'body' | 'header' | 'button' parameters: comp.parameters.map(p => ({ type: 'text', text: message.variables[p.key], })), })), }, }; const response = await axios.post(url, body, { headers: { Authorization: `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', }, }); return { messageId: response.data.messages[0].id, provider: 'whatsapp_business_api', status: response.data.messages[0].message_status, }; } async verify(): Promise<void> { // Fetch phone number info to verify credentials and phone number ID const url = `${this.baseUrl}/${this.phoneNumberId}`; const response = await axios.get(url, { headers: { Authorization: `Bearer ${this.apiKey}` }, }); if (!response.data.id) { throw new Error('WhatsApp Business API verification failed — phone number not found'); } } }

Template definition type

interface WhatsAppTemplateDefinition { name: string; // e.g. "hitl_review_request" languageCode?: string; // e.g. "en" (default), "hi", "ta" components?: WhatsAppComponent[]; } interface WhatsAppComponent { type: 'body' | 'header' | 'button'; parameters: { key: string }[]; // keys into message.variables }

Templates are defined in apps/notifications/src/templates/whatsapp-templates.ts and referenced by the WhatsApp handler.


Test Cases

Unit tests (packages/provider-whatsapp/src/providers/whatsapp-business.test.ts)

TestApproach
sendTemplate() POSTs to correct Graph API URLMock axios.post; assert URL contains phoneNumberId
sendTemplate() sends correct Authorization headerAssert Bearer ${apiKey}
sendTemplate() maps variables to template componentsAssert parameters[0].text === variables['{{1}}']
sendTemplate() returns messageId from responseMock { messages: [{ id: 'wamid.xxx' }] }
sendTemplate() throws on 401 (invalid token)Mock 401 response; assert error propagated
sendTemplate() throws on unknown template nameMock 400 (#131030); assert propagated
verify() fetches phone number metadataMock axios.get; assert URL and header
verify() throws when id absentMock response {}; assert throws

Integration tests

TestApproach
Send template via Meta Graph API sandboxUse Meta test phone number; send hello_world template; assert wamid returned
Platform default used when no tenant rowSeed no notification_providers row; assert WhatsAppBusinessProvider uses env vars
Tenant provider used when verifiedSeed verified row; assert tenant phoneNumberId used

Meta test numbers

Meta provides test business phone numbers in the Meta Developer Console (Apps → WhatsApp → Getting Started). Test numbers can send to up to 5 registered recipient numbers for free.


© 2026 Leadmetrics — Internal use only