Skip to content
Playground

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 types
import type {
WebhookPayload,
WhatsAppMessage,
TextMessage,
StatusWebhook,
MessageStatus
} from 'meta-cloud-api/types';
// Import from webhook module
import 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 guard
function 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 messages
processor.onMessage(MessageTypesEnum.Text, async (message) => {
// message is automatically TextMessage
await handleTextMessage(message.text.body);
});
// Images
processor.onMessage(MessageTypesEnum.Image, async (message) => {
// message is automatically ImageMessage
await downloadImage(message.image.id);
});
// Interactive
processor.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 lifecycle
type 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 participants
type 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 settings
interface 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 status
type 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 Echoes
interface 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) => void
type 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}`);
});

Source Code

View the source code on GitHub: