Webhook Types Reference
Complete reference for all webhook-related TypeScript types in the SDK. These types provide type-safe webhook event handling with discriminated unions for precise type narrowing.
Import Instructions
// Import webhook typesimport type { WebhookPayload, WhatsAppMessage, TextMessage, StatusWebhook, MessageStatus} from 'meta-cloud-api/types';
// Import from webhook moduleimport type { WebhookValue, MessageWebhookValue, StatusWebhookValue} from 'meta-cloud-api/types';Top-Level Webhook Structure
WebhookPayload
Root webhook payload received from WhatsApp.
interface WebhookPayload { object: 'whatsapp_business_account'; entry: Array<{ id: string; // WABA ID changes: Array< | { value: WebhookValue; field: 'messages'; } | WebhookFieldValue // Account updates, template status, etc. >; }>;}Raw Webhook Example:
{ "object": "whatsapp_business_account", "entry": [{ "id": "WABA_ID", "changes": [{ "value": { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "15551234567", "phone_number_id": "PHONE_ID" }, "messages": [{ "from": "15559876543", "id": "wamid.ABC123...", "timestamp": "1234567890", "type": "text", "text": { "body": "Hello!" } }] }, "field": "messages" }] }]}Webhook Value Types
WebhookValue
Discriminated union of webhook content types.
type WebhookValue = | MessageWebhookValue | StatusWebhookValue | ErrorWebhookValue;MessageWebhookValue
Webhook containing incoming messages.
interface MessageWebhookValue { messaging_product: 'whatsapp'; metadata: WebhookMetadata; contacts: Array<WebhookContact>; messages: Array<WhatsAppMessage>;}StatusWebhookValue
Webhook containing message status updates.
interface StatusWebhookValue { messaging_product: 'whatsapp'; metadata: WebhookMetadata; statuses: Array<StatusWebhook>;}ErrorWebhookValue
Webhook containing errors.
interface ErrorWebhookValue { messaging_product: 'whatsapp'; metadata: WebhookMetadata; errors: Array<WebhookError>;}Common Webhook Types
WebhookMetadata
Phone number metadata in all webhooks.
interface WebhookMetadata { display_phone_number: string; // Business phone number phone_number_id: string; // Phone number ID}WebhookContact
Contact information in message webhooks.
interface WebhookContact { wa_id: string; // WhatsApp ID profile: { name: string; // Profile name }; identity_key_hash?: string; // End-to-end encryption key}WebhookError
Error information structure.
interface WebhookError { code: number; title: string; message: string; error_data?: { details: string; }; href?: string; // Link to documentation}Example Error:
{ "code": 131026, "title": "Message Undeliverable", "message": "Message failed to send because more than 24 hours have passed", "error_data": { "details": "Message failed to send because more than 24 hours have passed since the customer last replied to this number." }}Incoming Message Types
WhatsAppMessage
Discriminated union of all incoming message types.
type WhatsAppMessage = | TextMessage | ImageMessage | VideoMessage | AudioMessage | DocumentMessage | StickerMessage | InteractiveMessage | ButtonMessage | LocationMessage | ContactsMessage | ReactionMessage | OrderMessage | SystemMessage | UnsupportedMessage | GroupMessage;BaseMessage
Common properties for all messages.
interface BaseMessage { from: string; // Sender's phone number id: string; // Message ID (wamid.ABC123...) timestamp: string; // Unix timestamp}TextMessage
Incoming text message.
interface TextMessage extends BaseMessage { type: MessageTypesEnum.Text; text: { body: string; }; context?: ForwardedContext | ProductContext; referral?: ReferralInfo;}Usage Example:
import { MessageTypesEnum } from 'meta-cloud-api/enums';
processor.onMessage(MessageTypesEnum.Text, async (message) => { // TypeScript knows message is TextMessage console.log(`Received: ${message.text.body}`); console.log(`From: ${message.from}`);
// Check if it's a forwarded message if (message.context?.forwarded) { console.log('This message was forwarded'); }});ImageMessage
Incoming image message.
interface ImageMessage extends BaseMessage { type: MessageTypesEnum.Image; image: { caption?: string; mime_type: string; sha256: string; id: string; // Media ID for download url: string; // Temporary download URL }; context?: ForwardedContext; referral?: ReferralInfo;}Usage Example:
processor.onMessage(MessageTypesEnum.Image, async (message) => { console.log(`Image caption: ${message.image.caption}`); console.log(`Media ID: ${message.image.id}`); console.log(`MIME type: ${message.image.mime_type}`);
// Download the image const mediaUrl = await client.media.getUrl(message.image.id); // ... download from mediaUrl});VideoMessage
Incoming video message.
interface VideoMessage extends BaseMessage { type: MessageTypesEnum.Video; video: { caption?: string; mime_type: string; sha256: string; id: string; url: string; }; context?: ForwardedContext; referral?: ReferralInfo;}AudioMessage
Incoming audio message.
interface AudioMessage extends BaseMessage { type: MessageTypesEnum.Audio; audio: { mime_type: string; sha256: string; id: string; url: string; voice: boolean; // true for voice notes }; referral?: ReferralInfo;}DocumentMessage
Incoming document message.
interface DocumentMessage extends BaseMessage { type: MessageTypesEnum.Document; document: { caption?: string; filename: string; mime_type: string; sha256: string; id: string; url: string; }; referral?: ReferralInfo;}StickerMessage
Incoming sticker message.
interface StickerMessage extends BaseMessage { type: MessageTypesEnum.Sticker; sticker: { mime_type: string; sha256: string; id: string; url: string; animated: boolean; }; referral?: ReferralInfo;}Interactive Message Types
InteractiveMessage
Discriminated union for interactive replies.
type InteractiveMessage = | InteractiveListReplyMessage | InteractiveButtonReplyMessage | InteractiveNfmReplyMessage;InteractiveButtonReplyMessage
Button click reply.
interface InteractiveButtonReplyMessage extends BaseMessage { type: MessageTypesEnum.Interactive; context: ReplyContext; interactive: { type: 'button_reply'; button_reply: { id: string; // Button ID you specified title: string; // Button text }; };}Usage Example:
processor.onMessage(MessageTypesEnum.Interactive, async (message) => { if (message.interactive.type === 'button_reply') { const buttonId = message.interactive.button_reply.id;
switch (buttonId) { case 'confirm': console.log('User confirmed'); break; case 'cancel': console.log('User cancelled'); break; } }});InteractiveListReplyMessage
List selection reply.
interface InteractiveListReplyMessage extends BaseMessage { type: MessageTypesEnum.Interactive; context: ReplyContext; interactive: { type: 'list_reply'; list_reply: { id: string; // Row ID you specified title: string; // Row title description?: string; // Row description }; };}InteractiveNfmReplyMessage
Flow (NFM) response.
interface InteractiveNfmReplyMessage extends BaseMessage { type: MessageTypesEnum.Interactive; context: ReplyContext; interactive: { type: 'nfm_reply'; nfm_reply: { name: string; // Flow name body: string; // Response body response_json: string; // JSON string with flow data }; };}Usage Example:
processor.onMessage(MessageTypesEnum.Interactive, async (message) => { if (message.interactive.type === 'nfm_reply') { const flowData = JSON.parse(message.interactive.nfm_reply.response_json); console.log('Flow response:', flowData); }});ButtonMessage
Quick reply button press.
interface ButtonMessage extends BaseMessage { type: MessageTypesEnum.Button; context: ReplyContext; button: { payload: string; // Button payload/ID text: string; // Button text };}Location & Contact Messages
LocationMessage
Incoming location.
interface LocationMessage extends BaseMessage { type: MessageTypesEnum.Location; location: { latitude: number; longitude: number; name?: string; address?: string; url?: string; }; referral?: ReferralInfo;}Usage Example:
processor.onMessage(MessageTypesEnum.Location, async (message) => { const { latitude, longitude, name, address } = message.location; console.log(`Location: ${name} (${latitude}, ${longitude})`); console.log(`Address: ${address}`);});ContactsMessage
Incoming contact card (vCard).
interface ContactsMessage extends BaseMessage { type: MessageTypesEnum.Contacts; contacts: Array<{ addresses?: Array<{ city?: string; country?: string; country_code?: string; state?: string; street?: string; type?: string; zip?: string; }>; birthday?: string; emails?: Array<{ email: string; type?: string; }>; name: { formatted_name: string; first_name?: string; last_name?: string; middle_name?: string; suffix?: string; prefix?: string; }; org?: { company?: string; department?: string; title?: string; }; phones?: Array<{ phone: string; wa_id?: string; type?: string; }>; urls?: Array<{ url: string; type?: string; }>; }>; referral?: ReferralInfo;}Other Message Types
ReactionMessage
Emoji reaction to a message.
interface ReactionMessage extends BaseMessage { type: MessageTypesEnum.Reaction; reaction: { message_id: string; // ID of message being reacted to emoji?: string; // Emoji (undefined if removed) };}Usage Example:
processor.onMessage(MessageTypesEnum.Reaction, async (message) => { const { message_id, emoji } = message.reaction;
if (emoji) { console.log(`User reacted with ${emoji} to ${message_id}`); } else { console.log(`User removed reaction from ${message_id}`); }});OrderMessage
Product order from catalog.
interface OrderMessage extends BaseMessage { type: MessageTypesEnum.Order; order: { catalog_id: string; text?: string; product_items: Array<{ product_retailer_id: string; quantity: number; item_price: number; currency: string; }>; };}SystemMessage
System notification (e.g., phone number change).
interface SystemMessage extends BaseMessage { type: MessageTypesEnum.System; system: { body: string; wa_id: string; type: 'user_changed_number'; };}UnsupportedMessage
Unsupported message type.
interface UnsupportedMessage extends BaseMessage { type: MessageTypesEnum.Unsupported; errors: Array<WebhookError>;}GroupMessage
Group message (any type with group_id).
type GroupMessage = { group_id: string;} & ( | TextMessage | ImageMessage | VideoMessage | AudioMessage | DocumentMessage | LocationMessage | ContactsMessage);Context Types
ForwardedContext
Context for forwarded messages.
interface ForwardedContext { forwarded?: true; frequently_forwarded?: true;}ProductContext
Context for product inquiry messages.
interface ProductContext { from: string; id: string; referred_product: { catalog_id: string; product_retailer_id: string; };}ReplyContext
Context for interactive/button replies.
interface ReplyContext { from: string; // Original message sender id: string; // Original message ID}ReferralInfo
Click-to-WhatsApp ad referral data.
interface ReferralInfo { source_url: string; source_id: string; source_type: 'ad'; body?: string; headline?: string; media_type?: 'image' | 'video'; image_url?: string; video_url?: string; thumbnail_url?: string; ctwa_clid?: string; welcome_message?: { text: string; };}Usage Example:
processor.onMessage(MessageTypesEnum.Text, async (message) => { if (message.referral) { console.log('User came from ad:', message.referral.source_url); console.log('Ad headline:', message.referral.headline); console.log('CTWA Click ID:', message.referral.ctwa_clid); }});Status Webhook Types
StatusWebhook
Message delivery status update.
interface StatusWebhook { id: string; // Message ID status: 'sent' | 'delivered' | 'read' | 'failed'; timestamp: string; recipient_id: string; // Recipient's phone number recipient_type?: 'group'; // Present for group messages recipient_participant_id?: string; // Group participant recipient_identity_key_hash?: string; biz_opaque_callback_data?: string; conversation?: ConversationInfo; pricing?: PricingInfo; errors?: Array<WebhookError>;}MessageStatus
Status enum for type-safe checking.
enum MessageStatus { DELIVERED = 'delivered', READ = 'read', SENT = 'sent', FAILED = 'failed',}ConversationInfo
Conversation pricing information.
interface ConversationInfo { id: string; expiration_timestamp?: string; origin: { type: | 'authentication' | 'authentication_international' | 'marketing' | 'marketing_lite' | 'referral_conversion' | 'service' | 'utility'; };}PricingInfo
Message pricing details.
interface PricingInfo { billable: boolean; pricing_model: 'CBP' | 'PMP'; // Conversation-based or Per-message type: 'regular' | 'free_customer_service' | 'free_entry_point'; category: | 'authentication' | 'authentication_international' | 'marketing' | 'marketing_lite' | 'referral_conversion' | 'service' | 'utility';}Usage Example:
processor.onStatus(async (status) => { console.log(`Message ${status.id} is ${status.status}`);
if (status.conversation) { console.log('Conversation type:', status.conversation.origin.type); }
if (status.pricing) { console.log('Billable:', status.pricing.billable); console.log('Category:', status.pricing.category); }
if (status.errors) { console.error('Status errors:', status.errors); }});Type Guards & Narrowing
The SDK uses discriminated unions for precise type narrowing:
import { MessageTypesEnum } from 'meta-cloud-api/enums';
function handleMessage(message: WhatsAppMessage) { // TypeScript narrows the type based on message.type switch (message.type) { case MessageTypesEnum.Text: // message is TextMessage console.log(message.text.body); break;
case MessageTypesEnum.Image: // message is ImageMessage console.log(message.image.id); break;
case MessageTypesEnum.Interactive: // message is InteractiveMessage if (message.interactive.type === 'button_reply') { // Further narrowed to InteractiveButtonReplyMessage console.log(message.interactive.button_reply.id); } break; }}
// Custom type guardfunction isTextMessage(message: WhatsAppMessage): message is TextMessage { return message.type === MessageTypesEnum.Text;}
if (isTextMessage(message)) { // TypeScript knows message is TextMessage console.log(message.text.body);}Webhook Handler Patterns
Pattern 1: Type-specific Handlers
import { MessageTypesEnum } from 'meta-cloud-api/enums';
// Text messagesprocessor.onMessage(MessageTypesEnum.Text, async (message) => { // message is automatically TextMessage await handleTextMessage(message.text.body);});
// Imagesprocessor.onMessage(MessageTypesEnum.Image, async (message) => { // message is automatically ImageMessage await downloadImage(message.image.id);});
// Interactiveprocessor.onMessage(MessageTypesEnum.Interactive, async (message) => { // message is automatically InteractiveMessage if (message.interactive.type === 'button_reply') { await handleButton(message.interactive.button_reply.id); }});Pattern 2: Catch-all Handler
processor.onMessage(MessageTypesEnum['*'], async (message) => { // message is WhatsAppMessage union type switch (message.type) { case MessageTypesEnum.Text: await handleText(message); break; case MessageTypesEnum.Image: await handleImage(message); break; // ... handle other types }});Pattern 3: Status Handler
import { MessageStatus } from 'meta-cloud-api/types';
processor.onStatus(async (status) => { switch (status.status) { case MessageStatus.DELIVERED: await markDelivered(status.id); break; case MessageStatus.READ: await markRead(status.id); break; case MessageStatus.FAILED: await handleFailure(status.id, status.errors); break; }});Related Documentation
- Enums Reference - All SDK enums
- Message Types Reference - Sending message types
- Webhooks Overview - Setting up webhooks
- Express Integration - Express.js webhook setup
- Next.js Integration - Next.js webhook setup
Source Code
View the source code on GitHub: