@warriorteam/messenger-sdk
v1.5.4
Published
TypeScript SDK for Facebook Messenger Platform API with Conversations support
Readme
@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-sdkRequirements
- 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'
});
// Send a text message
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; // Optional: Default page access token
version?: string; // Optional: API version (default: 'v23.0')
baseUrl?: string; // Optional: Custom base URL
timeout?: number; // Optional: Request timeout in ms (default: 30000)
maxRetries?: number; // Optional: Max retry attempts (default: 3)
});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'
});
// Use default token
await messenger.send.message({
recipient: { id: 'USER_PSID' },
message: { text: 'Hello from default page!' }
});
// Override token for this specific call
await messenger.send.message({
recipient: { id: 'USER_PSID' },
message: { text: 'Hello from different page!' }
}, {
accessToken: 'other_page_token'
});
// Works with all API methods
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); // Always present
console.log('Recipient ID:', result.recipient_id || 'Not provided'); // OptionalNote: 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
});
// Use the attachment ID to send
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
// Block a user from messaging (can still see page content)
await messenger.moderation.blockUser('USER_PSID');
// Ban a user completely (no messaging + no Facebook interactions)
await messenger.moderation.banUser('USER_PSID');
// Move conversation to spam folder
await messenger.moderation.moveToSpam('USER_PSID');
// Block and spam (common combo)
await messenger.moderation.blockAndSpam('USER_PSID');
// Multiple users at once
await messenger.moderation.blockUser(['USER_1', 'USER_2', 'USER_3']);
// Unblock/unban users
await messenger.moderation.unblockUser('USER_PSID');
await messenger.moderation.unbanUser('USER_PSID');Profile API
Get User Profile Information
// Get basic profile info (first_name, last_name, profile_pic)
const profile = await messenger.profile.getBasic('USER_PSID');
console.log(`Hello ${profile.first_name}!`);
// Get comprehensive profile with all fields
const fullProfile = await messenger.profile.getFull('USER_PSID');
console.log('Profile:', fullProfile);
// Returns: { id, name, first_name, last_name, profile_pic, locale, timezone, gender }
// Get specific fields only
const customProfile = await messenger.profile.get({
psid: 'USER_PSID',
fields: ['first_name', 'locale', 'timezone']
});
// Helper methods for common use cases
const nameInfo = await messenger.profile.getName('USER_PSID');
const profilePic = await messenger.profile.getProfilePicture('USER_PSID');Personalize Messages
// Use profile info to 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_metadatapages_read_engagementpages_messaging
For Instagram conversations:
instagram_basicinstagram_manage_messagespages_manage_metadata- Your app must be owned by a verified business
List Conversations
// List Messenger conversations
const conversations = await messenger.conversations.list('PAGE_ID', {
platform: 'messenger',
limit: 25
});
// List Instagram conversations
const igConversations = await messenger.conversations.list('PAGE_ID', {
platform: 'instagram',
limit: 25
});
// Find conversation with specific user
const conversationId = await messenger.conversations.findByUser(
'PAGE_ID',
'USER_INSTAGRAM_SCOPED_ID',
'instagram'
);Get Conversation Details
// Get conversation with messages and participants
const conversation = await messenger.conversations.get('CONVERSATION_ID', {
fields: ['messages', 'participants'],
limit: 20
});
// Access participants
conversation.participants?.data.forEach(participant => {
console.log(`${participant.name || participant.username} (${participant.id})`);
});
// Access messages
conversation.messages?.data.forEach(msg => {
console.log(`Message ID: ${msg.id}, Created: ${msg.created_time}`);
});Get Message Details
// Get full 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}`);
// Check attachments
if (message.attachments?.data) {
message.attachments.data.forEach(att => {
console.log(`Attachment: ${att.file_url || att.image_data?.url}`);
});
}
// Check reactions
if (message.reactions?.data) {
message.reactions.data.forEach(reaction => {
console.log(`${reaction.reaction} (${reaction.users.length} users)`);
});
}Get Recent Messages (Convenience Method)
// Get the 20 most recent messages with full details
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}`);
});Pagination
// Paginate through conversations
let after: string | undefined;
let hasMore = true;
while (hasMore) {
const conversations = await messenger.conversations.list('PAGE_ID', {
platform: 'messenger',
limit: 25,
after
});
// Process conversations
console.log(`Fetched ${conversations.data.length} conversations`);
// Check for next page
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';
// Process webhook with type-safe handlers
app.post('/webhook', express.json(), async (req, res) => {
const payload: GenericWebhookPayload = req.body;
await processWebhookEvents(payload, {
onMessage: async (event) => {
// TypeScript knows this is MessageWebhookEvent
console.log(`Received message: ${event.message.text}`);
},
onMessageEdit: async (event) => {
// TypeScript knows this is MessageEditWebhookEvent
console.log(`Message edited to: ${event.message_edit.text}`);
},
onMessageReaction: async (event) => {
// TypeScript knows this is MessageReactionWebhookEvent
console.log(`Reaction: ${event.reaction.reaction}`);
},
onMessagingPostback: async (event) => {
// TypeScript knows this is MessagingPostbackWebhookEvent
console.log(`Postback: ${event.postback.payload}`);
}
});
res.sendStatus(200);
});Manual Event Processing
// Extract events manually
const events = extractWebhookEvents(payload);
for (const event of events) {
const eventType = getWebhookEventType(event);
switch (eventType) {
case WebhookEventType.MESSAGE:
// Handle message event
break;
case WebhookEventType.MESSAGE_EDIT:
// Handle edit event
break;
// ... other cases
}
}
// Check what event types are in the payload (Messenger events)
const eventTypes = getWebhookPayloadEventTypes(payload);
console.log('Received Messenger event types:', eventTypes); // e.g., ['message', 'postback']
// For Page webhooks, use getPageWebhookEventTypes instead:
// const pagePayload: PageWebhookPayload = req.body;
// const pageEventTypes = getPageWebhookEventTypes(pagePayload);
// console.log('Received Page event types:', pageEventTypes); // e.g., [WebhookEventType.FEED, WebhookEventType.VIDEOS]Webhook Verification
The SDK provides utilities for both subscription verification and signature validation:
import {
verifyWebhookSubscription,
verifyWebhookSignature
} from '@warriorteam/messenger-sdk';
// Subscription verification (GET request)
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');
}
});
// Signature verification (POST request)
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});
}
// Process webhook events...
const payload = JSON.parse(req.body.toString());
// ... handle events
});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)) {
// TypeScript knows event.message exists
console.log(`Message: ${event.message.text}`);
} else if (isMessageEditEvent(event)) {
// TypeScript knows event.message_edit exists
console.log(`Edit: ${event.message_edit.text}`);
} else if (isMessagingPostbackEvent(event)) {
// TypeScript knows event.postback exists
console.log(`Postback: ${event.postback.payload}`);
}
}Supported Webhook Event Types
Messenger Platform Events
MESSAGE- User sends a messageMESSAGE_EDIT- User edits a sent messageMESSAGE_REACTION- User reacts to a messageMESSAGE_READ- User reads messages (read receipts)MESSAGING_FEEDBACK- User submits feedback via templatesMESSAGING_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';
// Handle Page webhooks
app.post('/webhook/page', express.json(), async (req, res) => {
const payload: PageWebhookPayload = req.body;
// Check what types of Page events are in this payload
const eventTypes = getPageWebhookEventTypes(payload);
console.log('Received Page event types:', eventTypes); // e.g., [WebhookEventType.FEED, WebhookEventType.VIDEOS]
for (const entry of payload.entry) {
for (const change of entry.changes || []) {
// Feed events (posts, photos, videos, status)
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!');
}
}
// Video encoding events
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...`);
}
}
// Live video events
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 {
// Subscription verification (GET)
@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');
}
// Messenger webhooks (POST)
@Post('messenger')
async handleMessenger(
@Body() payload: GenericWebhookPayload, // ← Correct type for Messenger
@Headers('x-hub-signature-256') signature: string,
@Res() res: Response
) {
// Verify signature
const verification = await verifyWebhookSignature(
JSON.stringify(payload),
signature,
process.env.APP_SECRET!
);
if (!verification.isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process events with type-safe handlers
await processWebhookEvents(payload, {
onMessage: async (event) => {
// event is MessageWebhookEvent - fully typed!
console.log(`From ${event.sender.id}: ${event.message.text}`);
},
onMessagingPostback: async (event) => {
// event is MessagingPostbackWebhookEvent
console.log(`Button clicked: ${event.postback.payload}`);
},
onMessageReaction: async (event) => {
// event is MessageReactionWebhookEvent
console.log(`Reaction: ${event.reaction.reaction}`);
},
});
return res.sendStatus(200);
}
// Page webhooks (POST)
@Post('page')
async handlePage(
@Body() payload: PageWebhookPayload, // ← Correct type for Page webhooks
@Headers('x-hub-signature-256') signature: string,
@Res() res: Response
) {
// Verify signature
const verification = await verifyWebhookSignature(
JSON.stringify(payload),
signature,
process.env.APP_SECRET!
);
if (!verification.isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process Page events
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 sendingupload-attachment.ts- Attachment handlingsend-template.ts- Template messagesuser-profile.ts- Profile API usagemoderation.ts- User moderationconversations.ts- Retrieve conversations and messageswebhook-handler.ts- Complete webhook setup with type safetymulti-tenant.ts- Token override for multi-tenant applicationssignature-verification.ts- Webhook security implementation
Development
# Install dependencies
npm install
# Build the project
npm run build
# Run tests
npm test
# Lint code
npm run lint
# Format code
npm run formatChangelog
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
fieldvalues toWebhookEventTypeenum for type safety - Similar to
getWebhookPayloadEventTypes()but forPageWebhookPayloadstructure - Properly exported from main package
- Returns
- 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,Messagetypes- 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
isProcessingfunction between video and live video webhooksisProcessingfrom videos module now exported asisVideoProcessingisProcessingfrom live-videos module now exported asisLiveVideoProcessing- Added explicit exports for all video and live video helper functions with unique names
- Similar to existing pattern used for
ReferralTypeandReferralSourceconflicts
- 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
RequestOptionsparameter
v1.1.0
- Added: User Profile API support
License
MIT
Support
For issues and questions, please visit our GitHub repository.
