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; }});Webhook Field Types
These types represent non-message webhook events delivered through different webhook fields.
Calls Types
type CallEventType = 'connect' | 'call_status' | 'media_update' | 'terminate';type CallDirection = 'business_initiated' | 'user_initiated';type CallStatusValue = 'ringing' | 'accepted';type CallTerminateStatus = 'Completed' | 'Failed';
interface CallEntry { id: string; event: CallEventType; timestamp: number; to?: string; // connect event from?: string; // connect event call_direction?: CallDirection; // connect event recipient_id?: string; // call_status event call_status?: CallStatusValue; // call_status event session?: CallSession; // media_update event status?: CallTerminateStatus; // terminate event start_time?: number; // terminate event end_time?: number; // terminate event duration?: number; // terminate event (seconds)}
interface CallsValue { messaging_product: 'whatsapp'; metadata: WebhookMetadata; calls: CallEntry[]; contacts?: CallContact[];}Usage Example:
processor.onCalls(async (whatsapp, { value }) => { for (const call of value.calls) { if (call.event === 'connect') { console.log(`Call from ${call.from}, direction: ${call.call_direction}`); } if (call.event === 'terminate') { console.log(`Call ended: ${call.status}, duration: ${call.duration}s`); } }});Group Types
// Group lifecycletype GroupLifecycleEventType = 'group_create' | 'group_delete';
interface GroupLifecycleEntry { timestamp: string; group_id: string; type: GroupLifecycleEventType; request_id: string; subject?: string; // group_create description?: string; // group_create invite_link?: string; // group_create join_approval_mode?: string; // group_create errors?: GroupWebhookError[];}
// Group participantstype GroupParticipantsEventType = | 'group_participants_add' | 'group_participants_remove' | 'group_join_request_created' | 'group_join_request_revoked';
interface GroupParticipantsEntry { timestamp: string; group_id: string; type: GroupParticipantsEventType; request_id?: string; reason?: 'invite_link' | 'admin_added' | string; added_participants?: Array<{ wa_id: string; input?: string }>; initiated_by?: 'business' | 'participant'; removed_participants?: Array<{ input: string }>; failed_participants?: Array<{ input: string; errors: GroupWebhookError[] }>; join_request_id?: string; wa_id?: string; errors?: GroupWebhookError[];}
// Group settingsinterface GroupSettingsEntry { timestamp: string; group_id: string; type: 'group_settings_update'; request_id?: string; profile_picture?: GroupSettingFieldResult; group_subject?: GroupSettingFieldResult; group_description?: GroupSettingFieldResult; errors?: GroupWebhookError[];}
// Group statustype GroupStatusEventType = 'group_suspend' | 'group_suspend_cleared';
interface GroupStatusEntry { timestamp: string; group_id: string; type: GroupStatusEventType;}Messaging Handover Types
interface MessagingHandoversValue { messaging_product: 'whatsapp'; recipient: { display_phone_number: string; phone_number_id: string; }; sender: { phone_number: string; }; timestamp: string; control_passed?: { metadata?: string; };}
interface StandbyValue { messaging_product: 'whatsapp'; [key: string]: unknown;}User Preferences Types
interface UserPreferenceEntry { wa_id: string; user_id: string; detail: string; category: 'marketing_messages' | string; value: 'stop' | string; timestamp: number; signup_id?: string;}
interface UserPreferencesValue { messaging_product: 'whatsapp'; metadata: WebhookMetadata; user_preferences: UserPreferenceEntry[]; contacts?: UserPreferencesContact[];}Usage Example:
processor.onUserPreferences(async (whatsapp, { value }) => { for (const pref of value.user_preferences) { if (pref.category === 'marketing_messages' && pref.value === 'stop') { console.log(`User ${pref.wa_id} opted out of marketing messages`); await updateMarketingConsent(pref.wa_id, false); } }});Partner Solutions Types
interface PartnerSolutionsValue { event: 'SOLUTION_CREATED' | string; solution_id: string; solution_status: 'INITIATED' | string;}Payment Configuration Types
interface PaymentConfigurationUpdateValue { configuration_name: string; provider_name: string; // e.g., "razorpay" provider_mid: string; status: string; // e.g., "Needs Testing" created_timestamp: number; updated_timestamp: number;}Marketing & Tracking Types
// Automatic events (lead gen / purchase from CTWA ads)interface AutomaticEvent { id: string; // WhatsApp message ID event_name: 'LeadSubmitted' | 'Purchase' | string; timestamp: number; ctwa_clid?: string; // Click-to-WhatsApp ad click ID custom_data?: { currency: string; // ISO 4217 (e.g., "USD") value: number; // Purchase amount };}
interface AutomaticEventsValue { messaging_product: 'whatsapp'; metadata: WebhookMetadata; automatic_events: AutomaticEvent[];}
// Tracking events (marketing message delivery/click tracking)interface TrackingEvent { event_name: string; // e.g., "sent" timestamp: number; tracking_data?: { click_id?: string; tracking_token?: string; };}
interface TrackingEventsValue { messaging_product: 'whatsapp'; metadata: WebhookMetadata; events: TrackingEvent[];}Account Settings Update Types
interface AccountSettingsUpdateValue { messaging_product: 'whatsapp'; timestamp: string; type: 'phone_number_settings' | string; phone_number_settings?: { phone_number_id: string; calling?: { status: 'ENABLED' | 'DISABLED' | string; call_icon_visibility?: string; callback_permission_status?: 'ENABLED' | 'DISABLED' | string; call_hours?: { status: string }; sip?: { status: string }; }; };}Message Echoes Types
// SMB Message Echoes (WhatsApp Business)interface SmbMessageEchoesValue { metadata: WebhookMetadata; errors?: Array<{ message: string; error_data?: { details: string } }>; message_echoes: Array<{ from: string; to: string; id: string; timestamp: string; text?: { body: string }; // ... other message type fields }>;}
// Messenger-style Message Echoesinterface MessageEchoesValue { messaging: Array<{ sender: { id: string }; recipient: { id: string }; timestamp: number; message: { is_echo: true; app_id: number; metadata?: string; mid: string; text?: string; attachments?: Array<{ type: string; url?: string }>; }; }>;}Handler Context Type
All handlers receive a third context parameter:
type WebhookHandlerContext = { headers: Headers; // Original request headers rawBody: string; // Original body string before parsing method: string; // HTTP method url: string; // Request URL};
// Handler signature: (whatsapp, processed, context) => voidtype MessageHandler = ( whatsapp: WhatsApp, processed: ProcessedMessage, context: WebhookHandlerContext,) => void | Promise<void>;ProcessedMessage Type
Message handlers receive a ProcessedMessage with metadata:
type ProcessedMessage = { wabaId: string; phoneNumberId: string; displayPhoneNumber: string; profileName: string; message: WhatsAppMessage; messageId: string;};Usage Example:
processor.onText(async (whatsapp, processed, context) => { console.log(`From: ${processed.profileName} (${processed.message.from})`); console.log(`Phone Number ID: ${processed.phoneNumberId}`); console.log(`Text: ${processed.message.text.body}`); console.log(`Request URL: ${context.url}`);});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: