Skip to content

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;
}
});

Source Code

View the source code on GitHub: