@warriorteam/messenger-sdk
A modern TypeScript SDK for the Facebook Messenger Platform API, designed with simplicity and type safety in mind.
Features
- π Zero runtime dependencies - Built with native fetch
- π Full TypeScript support - Complete type definitions with discriminated unions
- π Dual module support - ESM and CommonJS
- β
Comprehensive validation - Built-in message and template validation
- π‘οΈ Error handling - Detailed error types and messages
- π Complete API coverage - Send API, Templates, Attachments, Moderation, Profile, Conversations
- π¬ Conversations API - Retrieve conversation history and messages for Messenger & Instagram
- π Webhook utilities - Complete webhook type system and signature verification
- π― Token override - Per-method access token override support
- π§ Type-safe webhooks - Discriminated unions for proper TypeScript narrowing
Installation
npm install @warriorteam/messenger-sdk
Requirements
- Node.js 18+ (for native fetch support)
- A Facebook Page Access Token
Quick Start
import { Messenger } from '@warriorteam/messenger-sdk';
const messenger = new Messenger({
accessToken: 'YOUR_PAGE_ACCESS_TOKEN',
version: 'v23.0'
});
const result = await messenger.send.message({
recipient: { id: 'USER_PSID' },
message: { text: 'Hello from RedAI Messenger SDK!' }
});
console.log('Message sent:', result.message_id);
API Reference
Client Configuration
const messenger = new Messenger({
accessToken?: string;
version?: string;
baseUrl?: string;
timeout?: number;
maxRetries?: number;
});
Token Override Support
Every API method supports per-call access token override, perfect for multi-tenant applications:
const messenger = new Messenger({
accessToken: 'default_page_token'
});
await messenger.send.message({
recipient: { id: 'USER_PSID' },
message: { text: 'Hello from default page!' }
});
await messenger.send.message({
recipient: { id: 'USER_PSID' },
message: { text: 'Hello from different page!' }
}, {
accessToken: 'other_page_token'
});
const profile = await messenger.profile.getBasic('USER_PSID', {
accessToken: 'specific_token'
});
Send API
Send Text Message
await messenger.send.message({
recipient: { id: 'USER_PSID' },
message: { text: 'Hello World!' }
});
Send Message with Quick Replies
await messenger.send.message({
recipient: { id: 'USER_PSID' },
message: {
text: 'Pick a color:',
quick_replies: [
{ content_type: 'text', title: 'Red', payload: 'PICKED_RED' },
{ content_type: 'text', title: 'Blue', payload: 'PICKED_BLUE' }
]
}
});
Send Typing Indicators
await messenger.send.typingOn('USER_PSID');
await messenger.send.typingOff('USER_PSID');
await messenger.send.markSeen('USER_PSID');
Response Format
The Send API response includes a message_id and optionally a recipient_id:
const result = await messenger.send.message({...});
console.log('Message ID:', result.message_id);
console.log('Recipient ID:', result.recipient_id || 'Not provided');
Note: recipient_id is only included when using recipient.id (PSID). It's not included when using recipient.user_ref or recipient.phone_number.
Attachments API
Upload Attachment from URL
const attachment = await messenger.attachments.upload({
type: 'image',
url: 'https://example.com/image.jpg',
is_reusable: true
});
await messenger.send.message({
recipient: { id: 'USER_PSID' },
message: {
attachment: {
type: 'image',
payload: { attachment_id: attachment.attachment_id }
}
}
});
Send Attachment Directly
await messenger.send.message({
recipient: { id: 'USER_PSID' },
message: {
attachment: {
type: 'image',
payload: { url: 'https://example.com/image.jpg' }
}
}
});
Templates API
Generic Template
await messenger.templates.generic({
recipient: { id: 'USER_PSID' },
elements: [{
title: 'Welcome',
subtitle: 'Check out our products',
image_url: 'https://example.com/image.jpg',
buttons: [{
type: 'web_url',
title: 'Shop Now',
url: 'https://example.com/shop'
}]
}]
});
Button Template
await messenger.templates.button({
recipient: { id: 'USER_PSID' },
text: 'What would you like to do?',
buttons: [{
type: 'postback',
title: 'Get Started',
payload: 'GET_STARTED'
}]
});
Moderation API
User Moderation
await messenger.moderation.blockUser('USER_PSID');
await messenger.moderation.banUser('USER_PSID');
await messenger.moderation.moveToSpam('USER_PSID');
await messenger.moderation.blockAndSpam('USER_PSID');
await messenger.moderation.blockUser(['USER_1', 'USER_2', 'USER_3']);
await messenger.moderation.unblockUser('USER_PSID');
await messenger.moderation.unbanUser('USER_PSID');
Profile API
Get User Profile Information
const profile = await messenger.profile.getBasic('USER_PSID');
console.log(`Hello ${profile.first_name}!`);
const fullProfile = await messenger.profile.getFull('USER_PSID');
console.log('Profile:', fullProfile);
const customProfile = await messenger.profile.get({
psid: 'USER_PSID',
fields: ['first_name', 'locale', 'timezone']
});
const nameInfo = await messenger.profile.getName('USER_PSID');
const profilePic = await messenger.profile.getProfilePicture('USER_PSID');
Personalize Messages
const profile = await messenger.profile.getName('USER_PSID');
const greeting = profile.first_name
? `Hello ${profile.first_name}! Welcome back!`
: 'Hello! Welcome back!';
await messenger.send.message({
recipient: { id: 'USER_PSID' },
messaging_type: 'RESPONSE',
message: { text: greeting }
});
Note: Profile API requires "Advanced User Profile Access" feature and user interaction to grant permissions.
Conversations API
Retrieve conversation history and messages between users and your Page or Instagram Business Account.
Permissions Required
For Messenger conversations:
pages_manage_metadata
pages_read_engagement
pages_messaging
For Instagram conversations:
instagram_basic
instagram_manage_messages
pages_manage_metadata
- Your app must be owned by a verified business
List Conversations
const conversations = await messenger.conversations.list('PAGE_ID', {
platform: 'messenger',
limit: 25
});
const igConversations = await messenger.conversations.list('PAGE_ID', {
platform: 'instagram',
limit: 25
});
const conversationId = await messenger.conversations.findByUser(
'PAGE_ID',
'USER_INSTAGRAM_SCOPED_ID',
'instagram'
);
Get Conversation Details
const conversation = await messenger.conversations.get('CONVERSATION_ID', {
fields: ['messages', 'participants'],
limit: 20
});
conversation.participants?.data.forEach(participant => {
console.log(`${participant.name || participant.username} (${participant.id})`);
});
conversation.messages?.data.forEach(msg => {
console.log(`Message ID: ${msg.id}, Created: ${msg.created_time}`);
});
Get Message Details
const message = await messenger.conversations.getMessage('MESSAGE_ID', {
fields: ['id', 'created_time', 'from', 'to', 'message', 'attachments', 'reactions', 'reply_to']
});
console.log(`From: ${message.from?.username}`);
console.log(`Text: ${message.message}`);
if (message.attachments?.data) {
message.attachments.data.forEach(att => {
console.log(`Attachment: ${att.file_url || att.image_data?.url}`);
});
}
if (message.reactions?.data) {
message.reactions.data.forEach(reaction => {
console.log(`${reaction.reaction} (${reaction.users.length} users)`);
});
}
Get Recent Messages (Convenience Method)
const messages = await messenger.conversations.getRecentMessages('CONVERSATION_ID');
messages.forEach(msg => {
const sender = msg.from?.name || msg.from?.username;
const text = msg.message || '(attachment)';
console.log(`${sender}: ${text}`);
});
let after: string | undefined;
let hasMore = true;
while (hasMore) {
const conversations = await messenger.conversations.list('PAGE_ID', {
platform: 'messenger',
limit: 25,
after
});
console.log(`Fetched ${conversations.data.length} conversations`);
if (conversations.paging?.cursors?.after) {
after = conversations.paging.cursors.after;
} else {
hasMore = false;
}
}
Important Limitations
- 20 Message Limit: You can only retrieve full details for the 20 most recent messages in a conversation. Older messages will return an error.
- Pending Messages: Conversations in the "pending" folder that are inactive for 30+ days are not returned.
- Private Keys: Accounts linked with private keys (email/phone) require Advanced Access approval to retrieve conversations.
Webhook Support
The SDK provides comprehensive webhook support with full TypeScript safety through discriminated unions.
Webhook Types and Processing
import {
processWebhookEvents,
extractWebhookEvents,
getWebhookEventType,
getWebhookPayloadEventTypes,
getPageWebhookEventTypes,
WebhookEventType,
GenericWebhookPayload,
PageWebhookPayload
} from '@warriorteam/messenger-sdk';
app.post('/webhook', express.json(), async (req, res) => {
const payload: GenericWebhookPayload = req.body;
await processWebhookEvents(payload, {
onMessage: async (event) => {
console.log(`Received message: ${event.message.text}`);
},
onMessageEdit: async (event) => {
console.log(`Message edited to: ${event.message_edit.text}`);
},
onMessageReaction: async (event) => {
console.log(`Reaction: ${event.reaction.reaction}`);
},
onMessagingPostback: async (event) => {
console.log(`Postback: ${event.postback.payload}`);
}
});
res.sendStatus(200);
});
Manual Event Processing
const events = extractWebhookEvents(payload);
for (const event of events) {
const eventType = getWebhookEventType(event);
switch (eventType) {
case WebhookEventType.MESSAGE:
break;
case WebhookEventType.MESSAGE_EDIT:
break;
}
}
const eventTypes = getWebhookPayloadEventTypes(payload);
console.log('Received Messenger event types:', eventTypes);
Webhook Verification
The SDK provides utilities for both subscription verification and signature validation:
import {
verifyWebhookSubscription,
verifyWebhookSignature
} from '@warriorteam/messenger-sdk';
app.get('/webhook', (req, res) => {
const challenge = verifyWebhookSubscription(
req.query as any,
process.env.VERIFY_TOKEN!
);
if (challenge) {
res.send(challenge);
} else {
res.status(403).send('Forbidden');
}
});
app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
const signature = req.get('X-Hub-Signature-256');
const result = await verifyWebhookSignature(
req.body,
signature,
process.env.APP_SECRET!
);
if (!result.isValid) {
return res.status(401).json({error: result.error});
}
const payload = JSON.parse(req.body.toString());
});
Type-Safe Event Handling
The SDK uses discriminated unions for perfect TypeScript support:
import {
MessengerWebhookEvent,
isMessageEvent,
isMessageEditEvent,
isMessagingPostbackEvent
} from '@warriorteam/messenger-sdk';
function handleWebhookEvent(event: MessengerWebhookEvent) {
if (isMessageEvent(event)) {
console.log(`Message: ${event.message.text}`);
} else if (isMessageEditEvent(event)) {
console.log(`Edit: ${event.message_edit.text}`);
} else if (isMessagingPostbackEvent(event)) {
console.log(`Postback: ${event.postback.payload}`);
}
}
Supported Webhook Event Types
Messenger Platform Events
MESSAGE - User sends a message
MESSAGE_EDIT - User edits a sent message
MESSAGE_REACTION - User reacts to a message
MESSAGE_READ - User reads messages (read receipts)
MESSAGING_FEEDBACK - User submits feedback via templates
MESSAGING_POSTBACK - User clicks buttons/quick replies
Page Webhook Events
FEED - Page feed changes (posts, photos, videos, status updates)
VIDEOS - Video encoding status changes (processing, ready, error)
LIVE_VIDEOS - Live video status changes (live, stopped, scheduled, VOD ready)
Page Webhook Type Helpers
The SDK provides specialized type guards and helpers for Page webhook events:
import {
isFeedEvent,
isVideoEvent,
isLiveVideoEvent,
isVideoProcessing,
isVideoReady,
isLiveVideoProcessing,
isLive,
extractVideoContext,
extractLiveVideoContext,
getPageWebhookEventTypes,
FeedActionVerb,
VideoStatus,
LiveVideoStatus,
PageWebhookPayload
} from '@warriorteam/messenger-sdk';
app.post('/webhook/page', express.json(), async (req, res) => {
const payload: PageWebhookPayload = req.body;
const eventTypes = getPageWebhookEventTypes(payload);
console.log('Received Page event types:', eventTypes);
for (const entry of payload.entry) {
for (const change of entry.changes || []) {
if (isFeedEvent(change)) {
console.log(`Feed ${change.value.verb}: ${change.value.item} by ${change.value.from.id}`);
if (change.value.verb === FeedActionVerb.ADD) {
console.log('New post created!');
}
}
if (isVideoEvent(change)) {
const context = extractVideoContext(entry.id, entry.time, change);
if (context.isReady) {
console.log(`Video ${context.videoId} is ready!`);
} else if (context.isProcessing) {
console.log(`Video ${context.videoId} is still processing...`);
}
}
if (isLiveVideoEvent(change)) {
const context = extractLiveVideoContext(entry.id, entry.time, change);
if (context.isLive) {
console.log(`Live stream ${context.videoId} is now live!`);
} else if (context.isVODReady) {
console.log(`Live stream ${context.videoId} VOD is ready!`);
}
}
}
}
res.sendStatus(200);
});
Note: Page webhooks use a different structure (entry[].changes[]) compared to Messenger webhooks (entry[].messaging[]). The SDK provides helpers for both.
Complete Controller Example (NestJS/Express)
Here's a complete TypeScript controller for handling webhooks with proper types:
import { Controller, Post, Body, Headers, Res, Get, Query } from '@nestjs/common';
import { Response } from 'express';
import {
GenericWebhookPayload,
PageWebhookPayload,
verifyWebhookSignature,
verifyWebhookSubscription,
processWebhookEvents,
isFeedEvent,
isVideoEvent,
isLiveVideoEvent,
} from '@warriorteam/messenger-sdk';
@Controller('webhook')
export class WebhookController {
@Get()
verifyWebhook(@Query() query: any, @Res() res: Response) {
const challenge = verifyWebhookSubscription(
query,
process.env.VERIFY_TOKEN!
);
if (challenge) {
return res.send(challenge);
}
return res.status(403).send('Forbidden');
}
@Post('messenger')
async handleMessenger(
@Body() payload: GenericWebhookPayload,
@Headers('x-hub-signature-256') signature: string,
@Res() res: Response
) {
const verification = await verifyWebhookSignature(
JSON.stringify(payload),
signature,
process.env.APP_SECRET!
);
if (!verification.isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
await processWebhookEvents(payload, {
onMessage: async (event) => {
console.log(`From ${event.sender.id}: ${event.message.text}`);
},
onMessagingPostback: async (event) => {
console.log(`Button clicked: ${event.postback.payload}`);
},
onMessageReaction: async (event) => {
console.log(`Reaction: ${event.reaction.reaction}`);
},
});
return res.sendStatus(200);
}
@Post('page')
async handlePage(
@Body() payload: PageWebhookPayload,
@Headers('x-hub-signature-256') signature: string,
@Res() res: Response
) {
const verification = await verifyWebhookSignature(
JSON.stringify(payload),
signature,
process.env.APP_SECRET!
);
if (!verification.isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
for (const entry of payload.entry) {
for (const change of entry.changes || []) {
if (isFeedEvent(change)) {
console.log(`Page feed: ${change.value.verb} ${change.value.item}`);
}
if (isVideoEvent(change)) {
console.log(`Video status: ${change.value.status.video_status}`);
}
if (isLiveVideoEvent(change)) {
console.log(`Live video status: ${change.value.status}`);
}
}
}
return res.sendStatus(200);
}
}
Key Types to Use:
GenericWebhookPayload - For Messenger Platform webhooks (entry[].messaging[])
PageWebhookPayload - For Page webhooks (entry[].changes[])
Error Handling
The SDK provides specific error types for different scenarios:
import {
MessengerAPIError,
MessengerNetworkError,
MessengerTimeoutError,
MessengerConfigError
} from '@warriorteam/messenger-sdk';
try {
await messenger.send.message({
recipient: { id: 'USER_PSID' },
message: { text: 'Hello!' }
});
} catch (error) {
if (error instanceof MessengerAPIError) {
console.error('API Error:', error.message, error.code);
} else if (error instanceof MessengerNetworkError) {
console.error('Network Error:', error.message);
} else if (error instanceof MessengerTimeoutError) {
console.error('Timeout Error:', error.timeout);
}
}
Examples
Check the examples/ directory for complete usage examples:
send-message.ts - Basic message sending
upload-attachment.ts - Attachment handling
send-template.ts - Template messages
user-profile.ts - Profile API usage
moderation.ts - User moderation
conversations.ts - Retrieve conversations and messages
webhook-handler.ts - Complete webhook setup with type safety
multi-tenant.ts - Token override for multi-tenant applications
signature-verification.ts - Webhook security implementation
Development
npm install
npm run build
npm test
npm run lint
npm run format
Changelog
v1.5.2 (Latest)
- Added:
getPageWebhookEventTypes() utility function for extracting event types from Page webhooks
- Returns
WebhookEventType[] array with proper enum values (e.g., [WebhookEventType.FEED, WebhookEventType.VIDEOS])
- Maps Page webhook
field values to WebhookEventType enum for type safety
- Similar to
getWebhookPayloadEventTypes() but for PageWebhookPayload structure
- Properly exported from main package
- Improved: Documentation with usage examples for Page webhook event type extraction
v1.5.1
- Added: Complete NestJS/Express webhook controller example in README
- Updated: WebhookEventType enum to include FEED, VIDEOS, LIVE_VIDEOS
- Improved: Documentation with clear type usage for webhook handlers and Page webhook utilities
v1.5.0
- Added: Conversations API for retrieving conversation history and messages
- List conversations for Messenger and Instagram
- Get conversation details with participants and messages
- Retrieve individual message details with attachments, reactions, and replies
- Convenience method for getting recent messages with full details
- Find conversations by user ID
- Full pagination support
- Support for both Messenger and Instagram platforms
- Token override support for multi-tenant applications
- Added: Comprehensive TypeScript types for Conversations API
Conversation, ConversationDetail, Message types
- Message attachments, reactions, shares, story replies
- Image/video data types for media attachments
- Request/response types for all operations
v1.4.2
- Fixed: Resolved export naming conflicts for
isProcessing function between video and live video webhooks
isProcessing from videos module now exported as isVideoProcessing
isProcessing from live-videos module now exported as isLiveVideoProcessing
- Added explicit exports for all video and live video helper functions with unique names
- Similar to existing pattern used for
ReferralType and ReferralSource conflicts
- Improved: Better TypeScript type safety with no export ambiguities
v1.4.x
- Added: Comprehensive Facebook Page webhook types
- Feed webhook events (posts, photos, videos, status updates)
- Video encoding webhook events (processing, ready, error states)
- Live video webhook events (live, stopped, scheduled, VOD ready)
- Added: Type guards and helper functions for all Page webhook events
- Added: Context extraction utilities for video events
v1.3.x
- Added: Token override support for multi-tenant applications
- Improved: All API methods now accept optional
RequestOptions parameter
v1.1.0
- Added: User Profile API support
License
MIT
Support
For issues and questions, please visit our GitHub repository.