@warriorteam/messenger-sdk 1.5.7 → 1.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,886 +1,886 @@
1
- # @warriorteam/messenger-sdk
2
-
3
- A modern TypeScript SDK for the Facebook Messenger Platform API, designed with simplicity and type safety in mind.
4
-
5
- ## Features
6
-
7
- - 🚀 **Zero runtime dependencies** - Built with native fetch
8
- - 📝 **Full TypeScript support** - Complete type definitions with discriminated unions
9
- - 🔄 **Dual module support** - ESM and CommonJS
10
- - ✅ **Comprehensive validation** - Built-in message and template validation
11
- - 🛡️ **Error handling** - Detailed error types and messages
12
- - 📚 **Complete API coverage** - Send API, Templates, Attachments, Moderation, Profile, Conversations
13
- - 💬 **Conversations API** - Retrieve conversation history and messages for Messenger & Instagram
14
- - 🔐 **Webhook utilities** - Complete webhook type system and signature verification
15
- - 🎯 **Token override** - Per-method access token override support
16
- - 🔧 **Type-safe webhooks** - Discriminated unions for proper TypeScript narrowing
17
-
18
- ## Installation
19
-
20
- ```bash
21
- npm install @warriorteam/messenger-sdk
22
- ```
23
-
24
- ## Requirements
25
-
26
- - Node.js 18+ (for native fetch support)
27
- - A Facebook Page Access Token
28
-
29
- ## Quick Start
30
-
31
- ```typescript
32
- import { Messenger } from '@warriorteam/messenger-sdk';
33
-
34
- const messenger = new Messenger({
35
- accessToken: 'YOUR_PAGE_ACCESS_TOKEN',
36
- version: 'v23.0'
37
- });
38
-
39
- // Send a text message
40
- const result = await messenger.send.message({
41
- recipient: { id: 'USER_PSID' },
42
- message: { text: 'Hello from RedAI Messenger SDK!' }
43
- });
44
-
45
- console.log('Message sent:', result.message_id);
46
- ```
47
-
48
- ## API Reference
49
-
50
- ### Client Configuration
51
-
52
- ```typescript
53
- const messenger = new Messenger({
54
- accessToken?: string; // Optional: Default page access token
55
- version?: string; // Optional: API version (default: 'v23.0')
56
- baseUrl?: string; // Optional: Custom base URL
57
- timeout?: number; // Optional: Request timeout in ms (default: 30000)
58
- maxRetries?: number; // Optional: Max retry attempts (default: 3)
59
- });
60
- ```
61
-
62
- ### Token Override Support
63
-
64
- Every API method supports per-call access token override, perfect for multi-tenant applications:
65
-
66
- ```typescript
67
- const messenger = new Messenger({
68
- accessToken: 'default_page_token'
69
- });
70
-
71
- // Use default token
72
- await messenger.send.message({
73
- recipient: { id: 'USER_PSID' },
74
- message: { text: 'Hello from default page!' }
75
- });
76
-
77
- // Override token for this specific call
78
- await messenger.send.message({
79
- recipient: { id: 'USER_PSID' },
80
- message: { text: 'Hello from different page!' }
81
- }, {
82
- accessToken: 'other_page_token'
83
- });
84
-
85
- // Works with all API methods
86
- const profile = await messenger.profile.getBasic('USER_PSID', {
87
- accessToken: 'specific_token'
88
- });
89
- ```
90
-
91
- ### Send API
92
-
93
- #### Send Text Message
94
-
95
- ```typescript
96
- await messenger.send.message({
97
- recipient: { id: 'USER_PSID' },
98
- message: { text: 'Hello World!' }
99
- });
100
- ```
101
-
102
- #### Send Message with Quick Replies
103
-
104
- ```typescript
105
- await messenger.send.message({
106
- recipient: { id: 'USER_PSID' },
107
- message: {
108
- text: 'Pick a color:',
109
- quick_replies: [
110
- { content_type: 'text', title: 'Red', payload: 'PICKED_RED' },
111
- { content_type: 'text', title: 'Blue', payload: 'PICKED_BLUE' }
112
- ]
113
- }
114
- });
115
- ```
116
-
117
- #### Sender Actions
118
-
119
- Manage conversation state and reactions:
120
-
121
- ```typescript
122
- // Typing indicators
123
- await messenger.send.typingOn('USER_PSID');
124
- await messenger.send.typingOff('USER_PSID');
125
-
126
- // Helper to toggle typing based on boolean
127
- await messenger.send.setTyping('USER_PSID', true);
128
-
129
- // Mark as read/seen
130
- await messenger.send.markRead('USER_PSID');
131
-
132
- // React to a message
133
- await messenger.send.addReaction('USER_PSID', {
134
- messageId: 'MESSAGE_ID',
135
- emoji: '❤️'
136
- });
137
-
138
- // Remove reaction
139
- await messenger.send.removeReaction('USER_PSID', 'MESSAGE_ID');
140
- ```
141
-
142
- #### Response Format
143
-
144
- The Send API response includes a `message_id` and optionally a `recipient_id`:
145
-
146
- ```typescript
147
- const result = await messenger.send.message({...});
148
- console.log('Message ID:', result.message_id); // Always present
149
- console.log('Recipient ID:', result.recipient_id || 'Not provided'); // Optional
150
- ```
151
-
152
- **Note**: `recipient_id` is only included when using `recipient.id` (PSID). It's not included when using `recipient.user_ref` or `recipient.phone_number`.
153
-
154
- ### Attachments API
155
-
156
- #### Upload Attachment from URL
157
-
158
- ```typescript
159
- const attachment = await messenger.attachments.upload({
160
- type: 'image',
161
- url: 'https://example.com/image.jpg',
162
- is_reusable: true
163
- });
164
-
165
- // Use the attachment ID to send
166
- await messenger.send.message({
167
- recipient: { id: 'USER_PSID' },
168
- message: {
169
- attachment: {
170
- type: 'image',
171
- payload: { attachment_id: attachment.attachment_id }
172
- }
173
- }
174
- });
175
- ```
176
-
177
- #### Send Attachment Directly
178
-
179
- ```typescript
180
- await messenger.send.message({
181
- recipient: { id: 'USER_PSID' },
182
- message: {
183
- attachment: {
184
- type: 'image',
185
- payload: { url: 'https://example.com/image.jpg' }
186
- }
187
- }
188
- });
189
- ```
190
-
191
- ### Templates API
192
-
193
- #### Generic Template
194
-
195
- ```typescript
196
- await messenger.templates.generic({
197
- recipient: { id: 'USER_PSID' },
198
- elements: [{
199
- title: 'Welcome',
200
- subtitle: 'Check out our products',
201
- image_url: 'https://example.com/image.jpg',
202
- buttons: [{
203
- type: 'web_url',
204
- title: 'Shop Now',
205
- url: 'https://example.com/shop'
206
- }]
207
- }]
208
- });
209
- ```
210
-
211
- #### Button Template
212
-
213
- ```typescript
214
- await messenger.templates.button({
215
- recipient: { id: 'USER_PSID' },
216
- text: 'What would you like to do?',
217
- buttons: [{
218
- type: 'postback',
219
- title: 'Get Started',
220
- payload: 'GET_STARTED'
221
- }]
222
- });
223
- ```
224
-
225
- ### Moderation API
226
-
227
- #### User Moderation
228
-
229
- ```typescript
230
- // Block a user from messaging (can still see page content)
231
- await messenger.moderation.blockUser('USER_PSID');
232
-
233
- // Ban a user completely (no messaging + no Facebook interactions)
234
- await messenger.moderation.banUser('USER_PSID');
235
-
236
- // Move conversation to spam folder
237
- await messenger.moderation.moveToSpam('USER_PSID');
238
-
239
- // Block and spam (common combo)
240
- await messenger.moderation.blockAndSpam('USER_PSID');
241
-
242
- // Multiple users at once
243
- await messenger.moderation.blockUser(['USER_1', 'USER_2', 'USER_3']);
244
-
245
- // Unblock/unban users
246
- await messenger.moderation.unblockUser('USER_PSID');
247
- await messenger.moderation.unbanUser('USER_PSID');
248
- ```
249
-
250
- ### Profile API
251
-
252
- #### Get User Profile Information
253
-
254
- ```typescript
255
- // Get basic profile info (first_name, last_name, profile_pic)
256
- const profile = await messenger.profile.getBasic('USER_PSID');
257
- console.log(`Hello ${profile.first_name}!`);
258
-
259
- // Get comprehensive profile with all fields
260
- const fullProfile = await messenger.profile.getFull('USER_PSID');
261
- console.log('Profile:', fullProfile);
262
- // Returns: { id, name, first_name, last_name, profile_pic, locale, timezone, gender }
263
-
264
- // Get specific fields only
265
- const customProfile = await messenger.profile.get({
266
- psid: 'USER_PSID',
267
- fields: ['first_name', 'locale', 'timezone']
268
- });
269
-
270
- // Helper methods for common use cases
271
- const nameInfo = await messenger.profile.getName('USER_PSID');
272
- const profilePic = await messenger.profile.getProfilePicture('USER_PSID');
273
- ```
274
-
275
- #### Personalize Messages
276
-
277
- ```typescript
278
- // Use profile info to personalize messages
279
- const profile = await messenger.profile.getName('USER_PSID');
280
- const greeting = profile.first_name
281
- ? `Hello ${profile.first_name}! Welcome back!`
282
- : 'Hello! Welcome back!';
283
-
284
- await messenger.send.message({
285
- recipient: { id: 'USER_PSID' },
286
- messaging_type: 'RESPONSE',
287
- message: { text: greeting }
288
- });
289
- ```
290
-
291
- **Note**: Profile API requires "Advanced User Profile Access" feature and user interaction to grant permissions.
292
-
293
- ### Conversations API
294
-
295
- Retrieve conversation history and messages between users and your Page or Instagram Business Account.
296
-
297
- #### Permissions Required
298
-
299
- **For Messenger conversations:**
300
- - `pages_manage_metadata`
301
- - `pages_read_engagement`
302
- - `pages_messaging`
303
-
304
- **For Instagram conversations:**
305
- - `instagram_basic`
306
- - `instagram_manage_messages`
307
- - `pages_manage_metadata`
308
- - Your app must be owned by a verified business
309
-
310
- #### List Conversations
311
-
312
- ```typescript
313
- // List Messenger conversations
314
- const conversations = await messenger.conversations.list('PAGE_ID', {
315
- platform: 'messenger',
316
- limit: 25
317
- });
318
-
319
- // List Instagram conversations
320
- const igConversations = await messenger.conversations.list('PAGE_ID', {
321
- platform: 'instagram',
322
- limit: 25
323
- });
324
-
325
- // Find conversation with specific user
326
- const conversationId = await messenger.conversations.findByUser(
327
- 'PAGE_ID',
328
- 'USER_INSTAGRAM_SCOPED_ID',
329
- 'instagram'
330
- );
331
- ```
332
-
333
- #### Get Conversation Details
334
-
335
- ```typescript
336
- // Get conversation with messages and participants
337
- const conversation = await messenger.conversations.get('CONVERSATION_ID', {
338
- fields: ['messages', 'participants'],
339
- limit: 20
340
- });
341
-
342
- // Access participants
343
- conversation.participants?.data.forEach(participant => {
344
- console.log(`${participant.name || participant.username} (${participant.id})`);
345
- });
346
-
347
- // Access messages
348
- conversation.messages?.data.forEach(msg => {
349
- console.log(`Message ID: ${msg.id}, Created: ${msg.created_time}`);
350
- });
351
- ```
352
-
353
- #### Get Message Details
354
-
355
- ```typescript
356
- // Get full message details
357
- const message = await messenger.conversations.getMessage('MESSAGE_ID', {
358
- fields: ['id', 'created_time', 'from', 'to', 'message', 'attachments', 'reactions', 'reply_to']
359
- });
360
-
361
- console.log(`From: ${message.from?.username}`);
362
- console.log(`Text: ${message.message}`);
363
-
364
- // Check attachments
365
- if (message.attachments?.data) {
366
- message.attachments.data.forEach(att => {
367
- console.log(`Attachment: ${att.file_url || att.image_data?.url}`);
368
- });
369
- }
370
-
371
- // Check reactions
372
- if (message.reactions?.data) {
373
- message.reactions.data.forEach(reaction => {
374
- console.log(`${reaction.reaction} (${reaction.users.length} users)`);
375
- });
376
- }
377
- ```
378
-
379
- #### Get Recent Messages (Convenience Method)
380
-
381
- ```typescript
382
- // Get the 20 most recent messages with full details
383
- const messages = await messenger.conversations.getRecentMessages('CONVERSATION_ID');
384
-
385
- messages.forEach(msg => {
386
- const sender = msg.from?.name || msg.from?.username;
387
- const text = msg.message || '(attachment)';
388
- console.log(`${sender}: ${text}`);
389
- });
390
- ```
391
-
392
- #### Pagination
393
-
394
- ```typescript
395
- // Paginate through conversations
396
- let after: string | undefined;
397
- let hasMore = true;
398
-
399
- while (hasMore) {
400
- const conversations = await messenger.conversations.list('PAGE_ID', {
401
- platform: 'messenger',
402
- limit: 25,
403
- after
404
- });
405
-
406
- // Process conversations
407
- console.log(`Fetched ${conversations.data.length} conversations`);
408
-
409
- // Check for next page
410
- if (conversations.paging?.cursors?.after) {
411
- after = conversations.paging.cursors.after;
412
- } else {
413
- hasMore = false;
414
- }
415
- }
416
- ```
417
-
418
- #### Important Limitations
419
-
420
- - **20 Message Limit**: You can only retrieve full details for the **20 most recent messages** in a conversation. Older messages will return an error.
421
- - **Pending Messages**: Conversations in the "pending" folder that are inactive for 30+ days are not returned.
422
- - **Private Keys**: Accounts linked with private keys (email/phone) require Advanced Access approval to retrieve conversations.
423
-
424
- ## Webhook Support
425
-
426
- The SDK provides comprehensive webhook support with full TypeScript safety through discriminated unions.
427
-
428
- ### Webhook Types and Processing
429
-
430
- ```typescript
431
- import {
432
- processWebhookEvents,
433
- extractWebhookEvents,
434
- getWebhookEventType,
435
- getWebhookPayloadEventTypes,
436
- getPageWebhookEventTypes,
437
- WebhookEventType,
438
- GenericWebhookPayload,
439
- PageWebhookPayload
440
- } from '@warriorteam/messenger-sdk';
441
-
442
- // Process webhook with type-safe handlers
443
- app.post('/webhook', express.json(), async (req, res) => {
444
- const payload: GenericWebhookPayload = req.body;
445
-
446
- await processWebhookEvents(payload, {
447
- onMessage: async (event) => {
448
- // TypeScript knows this is MessageWebhookEvent
449
- console.log(`Received message: ${event.message.text}`);
450
- },
451
- onMessageEdit: async (event) => {
452
- // TypeScript knows this is MessageEditWebhookEvent
453
- console.log(`Message edited to: ${event.message_edit.text}`);
454
- },
455
- onMessageReaction: async (event) => {
456
- // TypeScript knows this is MessageReactionWebhookEvent
457
- console.log(`Reaction: ${event.reaction.reaction}`);
458
- },
459
- onMessagingPostback: async (event) => {
460
- // TypeScript knows this is MessagingPostbackWebhookEvent
461
- console.log(`Postback: ${event.postback.payload}`);
462
- }
463
- });
464
-
465
- res.sendStatus(200);
466
- });
467
- ```
468
-
469
- ### Manual Event Processing
470
-
471
- ```typescript
472
- // Extract events manually
473
- const events = extractWebhookEvents(payload);
474
- for (const event of events) {
475
- const eventType = getWebhookEventType(event);
476
-
477
- switch (eventType) {
478
- case WebhookEventType.MESSAGE:
479
- // Handle message event
480
- break;
481
- case WebhookEventType.MESSAGE_EDIT:
482
- // Handle edit event
483
- break;
484
- // ... other cases
485
- }
486
- }
487
-
488
- // Check what event types are in the payload (Messenger events)
489
- const eventTypes = getWebhookPayloadEventTypes(payload);
490
- console.log('Received Messenger event types:', eventTypes); // e.g., ['message', 'postback']
491
-
492
- // For Page webhooks, use getPageWebhookEventTypes instead:
493
- // const pagePayload: PageWebhookPayload = req.body;
494
- // const pageEventTypes = getPageWebhookEventTypes(pagePayload);
495
- // console.log('Received Page event types:', pageEventTypes); // e.g., [WebhookEventType.FEED, WebhookEventType.VIDEOS]
496
- ```
497
-
498
- ### Webhook Verification
499
-
500
- The SDK provides utilities for both subscription verification and signature validation:
501
-
502
- ```typescript
503
- import {
504
- verifyWebhookSubscription,
505
- verifyWebhookSignature
506
- } from '@warriorteam/messenger-sdk';
507
-
508
- // Subscription verification (GET request)
509
- app.get('/webhook', (req, res) => {
510
- const challenge = verifyWebhookSubscription(
511
- req.query as any,
512
- process.env.VERIFY_TOKEN!
513
- );
514
-
515
- if (challenge) {
516
- res.send(challenge);
517
- } else {
518
- res.status(403).send('Forbidden');
519
- }
520
- });
521
-
522
- // Signature verification (POST request)
523
- app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
524
- const signature = req.get('X-Hub-Signature-256');
525
- const result = await verifyWebhookSignature(
526
- req.body,
527
- signature,
528
- process.env.APP_SECRET!
529
- );
530
-
531
- if (!result.isValid) {
532
- return res.status(401).json({error: result.error});
533
- }
534
-
535
- // Process webhook events...
536
- const payload = JSON.parse(req.body.toString());
537
- // ... handle events
538
- });
539
- ```
540
-
541
- ### Type-Safe Event Handling
542
-
543
- The SDK uses discriminated unions for perfect TypeScript support:
544
-
545
- ```typescript
546
- import {
547
- MessengerWebhookEvent,
548
- isMessageEvent,
549
- isMessageEditEvent,
550
- isMessagingPostbackEvent
551
- } from '@warriorteam/messenger-sdk';
552
-
553
- function handleWebhookEvent(event: MessengerWebhookEvent) {
554
- if (isMessageEvent(event)) {
555
- // TypeScript knows event.message exists
556
- console.log(`Message: ${event.message.text}`);
557
- } else if (isMessageEditEvent(event)) {
558
- // TypeScript knows event.message_edit exists
559
- console.log(`Edit: ${event.message_edit.text}`);
560
- } else if (isMessagingPostbackEvent(event)) {
561
- // TypeScript knows event.postback exists
562
- console.log(`Postback: ${event.postback.payload}`);
563
- }
564
- }
565
- ```
566
-
567
- ### Supported Webhook Event Types
568
-
569
- #### Messenger Platform Events
570
- - `MESSAGE` - User sends a message
571
- - `MESSAGE_EDIT` - User edits a sent message
572
- - `MESSAGE_REACTION` - User reacts to a message
573
- - `MESSAGE_READ` - User reads messages (read receipts)
574
- - `MESSAGING_FEEDBACK` - User submits feedback via templates
575
- - `MESSAGING_POSTBACK` - User clicks buttons/quick replies
576
-
577
- #### Page Webhook Events
578
- - `FEED` - Page feed changes (posts, photos, videos, status updates)
579
- - `VIDEOS` - Video encoding status changes (processing, ready, error)
580
- - `LIVE_VIDEOS` - Live video status changes (live, stopped, scheduled, VOD ready)
581
-
582
- ### Page Webhook Type Helpers
583
-
584
- The SDK provides specialized type guards and helpers for Page webhook events:
585
-
586
- ```typescript
587
- import {
588
- isFeedEvent,
589
- isVideoEvent,
590
- isLiveVideoEvent,
591
- isVideoProcessing,
592
- isVideoReady,
593
- isLiveVideoProcessing,
594
- isLive,
595
- extractVideoContext,
596
- extractLiveVideoContext,
597
- getPageWebhookEventTypes,
598
- FeedActionVerb,
599
- VideoStatus,
600
- LiveVideoStatus,
601
- PageWebhookPayload
602
- } from '@warriorteam/messenger-sdk';
603
-
604
- // Handle Page webhooks
605
- app.post('/webhook/page', express.json(), async (req, res) => {
606
- const payload: PageWebhookPayload = req.body;
607
-
608
- // Check what types of Page events are in this payload
609
- const eventTypes = getPageWebhookEventTypes(payload);
610
- console.log('Received Page event types:', eventTypes); // e.g., [WebhookEventType.FEED, WebhookEventType.VIDEOS]
611
-
612
- for (const entry of payload.entry) {
613
- for (const change of entry.changes || []) {
614
- // Feed events (posts, photos, videos, status)
615
- if (isFeedEvent(change)) {
616
- console.log(`Feed ${change.value.verb}: ${change.value.item} by ${change.value.from.id}`);
617
- if (change.value.verb === FeedActionVerb.ADD) {
618
- console.log('New post created!');
619
- }
620
- }
621
-
622
- // Video encoding events
623
- if (isVideoEvent(change)) {
624
- const context = extractVideoContext(entry.id, entry.time, change);
625
- if (context.isReady) {
626
- console.log(`Video ${context.videoId} is ready!`);
627
- } else if (context.isProcessing) {
628
- console.log(`Video ${context.videoId} is still processing...`);
629
- }
630
- }
631
-
632
- // Live video events
633
- if (isLiveVideoEvent(change)) {
634
- const context = extractLiveVideoContext(entry.id, entry.time, change);
635
- if (context.isLive) {
636
- console.log(`Live stream ${context.videoId} is now live!`);
637
- } else if (context.isVODReady) {
638
- console.log(`Live stream ${context.videoId} VOD is ready!`);
639
- }
640
- }
641
- }
642
- }
643
-
644
- res.sendStatus(200);
645
- });
646
- ```
647
-
648
- **Note**: Page webhooks use a different structure (`entry[].changes[]`) compared to Messenger webhooks (`entry[].messaging[]`). The SDK provides helpers for both.
649
-
650
- ### Complete Controller Example (NestJS/Express)
651
-
652
- Here's a complete TypeScript controller for handling webhooks with proper types:
653
-
654
- ```typescript
655
- import { Controller, Post, Body, Headers, Res, Get, Query } from '@nestjs/common';
656
- import { Response } from 'express';
657
- import {
658
- GenericWebhookPayload,
659
- PageWebhookPayload,
660
- verifyWebhookSignature,
661
- verifyWebhookSubscription,
662
- processWebhookEvents,
663
- isFeedEvent,
664
- isVideoEvent,
665
- isLiveVideoEvent,
666
- } from '@warriorteam/messenger-sdk';
667
-
668
- @Controller('webhook')
669
- export class WebhookController {
670
- // Subscription verification (GET)
671
- @Get()
672
- verifyWebhook(@Query() query: any, @Res() res: Response) {
673
- const challenge = verifyWebhookSubscription(
674
- query,
675
- process.env.VERIFY_TOKEN!
676
- );
677
-
678
- if (challenge) {
679
- return res.send(challenge);
680
- }
681
- return res.status(403).send('Forbidden');
682
- }
683
-
684
- // Messenger webhooks (POST)
685
- @Post('messenger')
686
- async handleMessenger(
687
- @Body() payload: GenericWebhookPayload, // ← Correct type for Messenger
688
- @Headers('x-hub-signature-256') signature: string,
689
- @Res() res: Response
690
- ) {
691
- // Verify signature
692
- const verification = await verifyWebhookSignature(
693
- JSON.stringify(payload),
694
- signature,
695
- process.env.APP_SECRET!
696
- );
697
-
698
- if (!verification.isValid) {
699
- return res.status(401).json({ error: 'Invalid signature' });
700
- }
701
-
702
- // Process events with type-safe handlers
703
- await processWebhookEvents(payload, {
704
- onMessage: async (event) => {
705
- // event is MessageWebhookEvent - fully typed!
706
- console.log(`From ${event.sender.id}: ${event.message.text}`);
707
- },
708
- onMessagingPostback: async (event) => {
709
- // event is MessagingPostbackWebhookEvent
710
- console.log(`Button clicked: ${event.postback.payload}`);
711
- },
712
- onMessageReaction: async (event) => {
713
- // event is MessageReactionWebhookEvent
714
- console.log(`Reaction: ${event.reaction.reaction}`);
715
- },
716
- });
717
-
718
- return res.sendStatus(200);
719
- }
720
-
721
- // Page webhooks (POST)
722
- @Post('page')
723
- async handlePage(
724
- @Body() payload: PageWebhookPayload, // ← Correct type for Page webhooks
725
- @Headers('x-hub-signature-256') signature: string,
726
- @Res() res: Response
727
- ) {
728
- // Verify signature
729
- const verification = await verifyWebhookSignature(
730
- JSON.stringify(payload),
731
- signature,
732
- process.env.APP_SECRET!
733
- );
734
-
735
- if (!verification.isValid) {
736
- return res.status(401).json({ error: 'Invalid signature' });
737
- }
738
-
739
- // Process Page events
740
- for (const entry of payload.entry) {
741
- for (const change of entry.changes || []) {
742
- if (isFeedEvent(change)) {
743
- console.log(`Page feed: ${change.value.verb} ${change.value.item}`);
744
- }
745
-
746
- if (isVideoEvent(change)) {
747
- console.log(`Video status: ${change.value.status.video_status}`);
748
- }
749
-
750
- if (isLiveVideoEvent(change)) {
751
- console.log(`Live video status: ${change.value.status}`);
752
- }
753
- }
754
- }
755
-
756
- return res.sendStatus(200);
757
- }
758
- }
759
- ```
760
-
761
- **Key Types to Use:**
762
- - `GenericWebhookPayload` - For Messenger Platform webhooks (`entry[].messaging[]`)
763
- - `PageWebhookPayload` - For Page webhooks (`entry[].changes[]`)
764
-
765
- ## Error Handling
766
-
767
- The SDK provides specific error types for different scenarios:
768
-
769
- ```typescript
770
- import {
771
- MessengerAPIError,
772
- MessengerNetworkError,
773
- MessengerTimeoutError,
774
- MessengerConfigError
775
- } from '@warriorteam/messenger-sdk';
776
-
777
- try {
778
- await messenger.send.message({
779
- recipient: { id: 'USER_PSID' },
780
- message: { text: 'Hello!' }
781
- });
782
- } catch (error) {
783
- if (error instanceof MessengerAPIError) {
784
- console.error('API Error:', error.message, error.code);
785
- } else if (error instanceof MessengerNetworkError) {
786
- console.error('Network Error:', error.message);
787
- } else if (error instanceof MessengerTimeoutError) {
788
- console.error('Timeout Error:', error.timeout);
789
- }
790
- }
791
- ```
792
-
793
- ## Examples
794
-
795
- Check the `examples/` directory for complete usage examples:
796
-
797
- - `send-message.ts` - Basic message sending
798
- - `upload-attachment.ts` - Attachment handling
799
- - `send-template.ts` - Template messages
800
- - `user-profile.ts` - Profile API usage
801
- - `moderation.ts` - User moderation
802
- - `conversations.ts` - Retrieve conversations and messages
803
- - `webhook-handler.ts` - Complete webhook setup with type safety
804
- - `multi-tenant.ts` - Token override for multi-tenant applications
805
- - `signature-verification.ts` - Webhook security implementation
806
-
807
- ## Development
808
-
809
- ```bash
810
- # Install dependencies
811
- npm install
812
-
813
- # Build the project
814
- npm run build
815
-
816
- # Run tests
817
- npm test
818
-
819
- # Lint code
820
- npm run lint
821
-
822
- # Format code
823
- npm run format
824
- ```
825
-
826
- ## Changelog
827
-
828
- ### v1.5.2 (Latest)
829
- - **Added**: `getPageWebhookEventTypes()` utility function for extracting event types from Page webhooks
830
- - Returns `WebhookEventType[]` array with proper enum values (e.g., `[WebhookEventType.FEED, WebhookEventType.VIDEOS]`)
831
- - Maps Page webhook `field` values to `WebhookEventType` enum for type safety
832
- - Similar to `getWebhookPayloadEventTypes()` but for `PageWebhookPayload` structure
833
- - Properly exported from main package
834
- - **Improved**: Documentation with usage examples for Page webhook event type extraction
835
-
836
- ### v1.5.1
837
- - **Added**: Complete NestJS/Express webhook controller example in README
838
- - **Updated**: WebhookEventType enum to include FEED, VIDEOS, LIVE_VIDEOS
839
- - **Improved**: Documentation with clear type usage for webhook handlers and Page webhook utilities
840
-
841
- ### v1.5.0
842
- - **Added**: Conversations API for retrieving conversation history and messages
843
- - List conversations for Messenger and Instagram
844
- - Get conversation details with participants and messages
845
- - Retrieve individual message details with attachments, reactions, and replies
846
- - Convenience method for getting recent messages with full details
847
- - Find conversations by user ID
848
- - Full pagination support
849
- - Support for both Messenger and Instagram platforms
850
- - Token override support for multi-tenant applications
851
- - **Added**: Comprehensive TypeScript types for Conversations API
852
- - `Conversation`, `ConversationDetail`, `Message` types
853
- - Message attachments, reactions, shares, story replies
854
- - Image/video data types for media attachments
855
- - Request/response types for all operations
856
-
857
- ### v1.4.2
858
- - **Fixed**: Resolved export naming conflicts for `isProcessing` function between video and live video webhooks
859
- - `isProcessing` from videos module now exported as `isVideoProcessing`
860
- - `isProcessing` from live-videos module now exported as `isLiveVideoProcessing`
861
- - Added explicit exports for all video and live video helper functions with unique names
862
- - Similar to existing pattern used for `ReferralType` and `ReferralSource` conflicts
863
- - **Improved**: Better TypeScript type safety with no export ambiguities
864
-
865
- ### v1.4.x
866
- - **Added**: Comprehensive Facebook Page webhook types
867
- - Feed webhook events (posts, photos, videos, status updates)
868
- - Video encoding webhook events (processing, ready, error states)
869
- - Live video webhook events (live, stopped, scheduled, VOD ready)
870
- - **Added**: Type guards and helper functions for all Page webhook events
871
- - **Added**: Context extraction utilities for video events
872
-
873
- ### v1.3.x
874
- - **Added**: Token override support for multi-tenant applications
875
- - **Improved**: All API methods now accept optional `RequestOptions` parameter
876
-
877
- ### v1.1.0
878
- - **Added**: User Profile API support
879
-
880
- ## License
881
-
882
- MIT
883
-
884
- ## Support
885
-
1
+ # @warriorteam/messenger-sdk
2
+
3
+ A modern TypeScript SDK for the Facebook Messenger Platform API, designed with simplicity and type safety in mind.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Zero runtime dependencies** - Built with native fetch
8
+ - 📝 **Full TypeScript support** - Complete type definitions with discriminated unions
9
+ - 🔄 **Dual module support** - ESM and CommonJS
10
+ - ✅ **Comprehensive validation** - Built-in message and template validation
11
+ - 🛡️ **Error handling** - Detailed error types and messages
12
+ - 📚 **Complete API coverage** - Send API, Templates, Attachments, Moderation, Profile, Conversations
13
+ - 💬 **Conversations API** - Retrieve conversation history and messages for Messenger & Instagram
14
+ - 🔐 **Webhook utilities** - Complete webhook type system and signature verification
15
+ - 🎯 **Token override** - Per-method access token override support
16
+ - 🔧 **Type-safe webhooks** - Discriminated unions for proper TypeScript narrowing
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @warriorteam/messenger-sdk
22
+ ```
23
+
24
+ ## Requirements
25
+
26
+ - Node.js 18+ (for native fetch support)
27
+ - A Facebook Page Access Token
28
+
29
+ ## Quick Start
30
+
31
+ ```typescript
32
+ import { Messenger } from '@warriorteam/messenger-sdk';
33
+
34
+ const messenger = new Messenger({
35
+ accessToken: 'YOUR_PAGE_ACCESS_TOKEN',
36
+ version: 'v23.0'
37
+ });
38
+
39
+ // Send a text message
40
+ const result = await messenger.send.message({
41
+ recipient: { id: 'USER_PSID' },
42
+ message: { text: 'Hello from RedAI Messenger SDK!' }
43
+ });
44
+
45
+ console.log('Message sent:', result.message_id);
46
+ ```
47
+
48
+ ## API Reference
49
+
50
+ ### Client Configuration
51
+
52
+ ```typescript
53
+ const messenger = new Messenger({
54
+ accessToken?: string; // Optional: Default page access token
55
+ version?: string; // Optional: API version (default: 'v23.0')
56
+ baseUrl?: string; // Optional: Custom base URL
57
+ timeout?: number; // Optional: Request timeout in ms (default: 30000)
58
+ maxRetries?: number; // Optional: Max retry attempts (default: 3)
59
+ });
60
+ ```
61
+
62
+ ### Token Override Support
63
+
64
+ Every API method supports per-call access token override, perfect for multi-tenant applications:
65
+
66
+ ```typescript
67
+ const messenger = new Messenger({
68
+ accessToken: 'default_page_token'
69
+ });
70
+
71
+ // Use default token
72
+ await messenger.send.message({
73
+ recipient: { id: 'USER_PSID' },
74
+ message: { text: 'Hello from default page!' }
75
+ });
76
+
77
+ // Override token for this specific call
78
+ await messenger.send.message({
79
+ recipient: { id: 'USER_PSID' },
80
+ message: { text: 'Hello from different page!' }
81
+ }, {
82
+ accessToken: 'other_page_token'
83
+ });
84
+
85
+ // Works with all API methods
86
+ const profile = await messenger.profile.getBasic('USER_PSID', {
87
+ accessToken: 'specific_token'
88
+ });
89
+ ```
90
+
91
+ ### Send API
92
+
93
+ #### Send Text Message
94
+
95
+ ```typescript
96
+ await messenger.send.message({
97
+ recipient: { id: 'USER_PSID' },
98
+ message: { text: 'Hello World!' }
99
+ });
100
+ ```
101
+
102
+ #### Send Message with Quick Replies
103
+
104
+ ```typescript
105
+ await messenger.send.message({
106
+ recipient: { id: 'USER_PSID' },
107
+ message: {
108
+ text: 'Pick a color:',
109
+ quick_replies: [
110
+ { content_type: 'text', title: 'Red', payload: 'PICKED_RED' },
111
+ { content_type: 'text', title: 'Blue', payload: 'PICKED_BLUE' }
112
+ ]
113
+ }
114
+ });
115
+ ```
116
+
117
+ #### Sender Actions
118
+
119
+ Manage conversation state and reactions:
120
+
121
+ ```typescript
122
+ // Typing indicators
123
+ await messenger.send.typingOn('USER_PSID');
124
+ await messenger.send.typingOff('USER_PSID');
125
+
126
+ // Helper to toggle typing based on boolean
127
+ await messenger.send.setTyping('USER_PSID', true);
128
+
129
+ // Mark as read/seen
130
+ await messenger.send.markRead('USER_PSID');
131
+
132
+ // React to a message
133
+ await messenger.send.addReaction('USER_PSID', {
134
+ messageId: 'MESSAGE_ID',
135
+ emoji: '❤️'
136
+ });
137
+
138
+ // Remove reaction
139
+ await messenger.send.removeReaction('USER_PSID', 'MESSAGE_ID');
140
+ ```
141
+
142
+ #### Response Format
143
+
144
+ The Send API response includes a `message_id` and optionally a `recipient_id`:
145
+
146
+ ```typescript
147
+ const result = await messenger.send.message({...});
148
+ console.log('Message ID:', result.message_id); // Always present
149
+ console.log('Recipient ID:', result.recipient_id || 'Not provided'); // Optional
150
+ ```
151
+
152
+ **Note**: `recipient_id` is only included when using `recipient.id` (PSID). It's not included when using `recipient.user_ref` or `recipient.phone_number`.
153
+
154
+ ### Attachments API
155
+
156
+ #### Upload Attachment from URL
157
+
158
+ ```typescript
159
+ const attachment = await messenger.attachments.upload({
160
+ type: 'image',
161
+ url: 'https://example.com/image.jpg',
162
+ is_reusable: true
163
+ });
164
+
165
+ // Use the attachment ID to send
166
+ await messenger.send.message({
167
+ recipient: { id: 'USER_PSID' },
168
+ message: {
169
+ attachment: {
170
+ type: 'image',
171
+ payload: { attachment_id: attachment.attachment_id }
172
+ }
173
+ }
174
+ });
175
+ ```
176
+
177
+ #### Send Attachment Directly
178
+
179
+ ```typescript
180
+ await messenger.send.message({
181
+ recipient: { id: 'USER_PSID' },
182
+ message: {
183
+ attachment: {
184
+ type: 'image',
185
+ payload: { url: 'https://example.com/image.jpg' }
186
+ }
187
+ }
188
+ });
189
+ ```
190
+
191
+ ### Templates API
192
+
193
+ #### Generic Template
194
+
195
+ ```typescript
196
+ await messenger.templates.generic({
197
+ recipient: { id: 'USER_PSID' },
198
+ elements: [{
199
+ title: 'Welcome',
200
+ subtitle: 'Check out our products',
201
+ image_url: 'https://example.com/image.jpg',
202
+ buttons: [{
203
+ type: 'web_url',
204
+ title: 'Shop Now',
205
+ url: 'https://example.com/shop'
206
+ }]
207
+ }]
208
+ });
209
+ ```
210
+
211
+ #### Button Template
212
+
213
+ ```typescript
214
+ await messenger.templates.button({
215
+ recipient: { id: 'USER_PSID' },
216
+ text: 'What would you like to do?',
217
+ buttons: [{
218
+ type: 'postback',
219
+ title: 'Get Started',
220
+ payload: 'GET_STARTED'
221
+ }]
222
+ });
223
+ ```
224
+
225
+ ### Moderation API
226
+
227
+ #### User Moderation
228
+
229
+ ```typescript
230
+ // Block a user from messaging (can still see page content)
231
+ await messenger.moderation.blockUser('USER_PSID');
232
+
233
+ // Ban a user completely (no messaging + no Facebook interactions)
234
+ await messenger.moderation.banUser('USER_PSID');
235
+
236
+ // Move conversation to spam folder
237
+ await messenger.moderation.moveToSpam('USER_PSID');
238
+
239
+ // Block and spam (common combo)
240
+ await messenger.moderation.blockAndSpam('USER_PSID');
241
+
242
+ // Multiple users at once
243
+ await messenger.moderation.blockUser(['USER_1', 'USER_2', 'USER_3']);
244
+
245
+ // Unblock/unban users
246
+ await messenger.moderation.unblockUser('USER_PSID');
247
+ await messenger.moderation.unbanUser('USER_PSID');
248
+ ```
249
+
250
+ ### Profile API
251
+
252
+ #### Get User Profile Information
253
+
254
+ ```typescript
255
+ // Get basic profile info (first_name, last_name, profile_pic)
256
+ const profile = await messenger.profile.getBasic('USER_PSID');
257
+ console.log(`Hello ${profile.first_name}!`);
258
+
259
+ // Get comprehensive profile with all fields
260
+ const fullProfile = await messenger.profile.getFull('USER_PSID');
261
+ console.log('Profile:', fullProfile);
262
+ // Returns: { id, name, first_name, last_name, profile_pic, locale, timezone, gender }
263
+
264
+ // Get specific fields only
265
+ const customProfile = await messenger.profile.get({
266
+ psid: 'USER_PSID',
267
+ fields: ['first_name', 'locale', 'timezone']
268
+ });
269
+
270
+ // Helper methods for common use cases
271
+ const nameInfo = await messenger.profile.getName('USER_PSID');
272
+ const profilePic = await messenger.profile.getProfilePicture('USER_PSID');
273
+ ```
274
+
275
+ #### Personalize Messages
276
+
277
+ ```typescript
278
+ // Use profile info to personalize messages
279
+ const profile = await messenger.profile.getName('USER_PSID');
280
+ const greeting = profile.first_name
281
+ ? `Hello ${profile.first_name}! Welcome back!`
282
+ : 'Hello! Welcome back!';
283
+
284
+ await messenger.send.message({
285
+ recipient: { id: 'USER_PSID' },
286
+ messaging_type: 'RESPONSE',
287
+ message: { text: greeting }
288
+ });
289
+ ```
290
+
291
+ **Note**: Profile API requires "Advanced User Profile Access" feature and user interaction to grant permissions.
292
+
293
+ ### Conversations API
294
+
295
+ Retrieve conversation history and messages between users and your Page or Instagram Business Account.
296
+
297
+ #### Permissions Required
298
+
299
+ **For Messenger conversations:**
300
+ - `pages_manage_metadata`
301
+ - `pages_read_engagement`
302
+ - `pages_messaging`
303
+
304
+ **For Instagram conversations:**
305
+ - `instagram_basic`
306
+ - `instagram_manage_messages`
307
+ - `pages_manage_metadata`
308
+ - Your app must be owned by a verified business
309
+
310
+ #### List Conversations
311
+
312
+ ```typescript
313
+ // List Messenger conversations
314
+ const conversations = await messenger.conversations.list('PAGE_ID', {
315
+ platform: 'messenger',
316
+ limit: 25
317
+ });
318
+
319
+ // List Instagram conversations
320
+ const igConversations = await messenger.conversations.list('PAGE_ID', {
321
+ platform: 'instagram',
322
+ limit: 25
323
+ });
324
+
325
+ // Find conversation with specific user
326
+ const conversationId = await messenger.conversations.findByUser(
327
+ 'PAGE_ID',
328
+ 'USER_INSTAGRAM_SCOPED_ID',
329
+ 'instagram'
330
+ );
331
+ ```
332
+
333
+ #### Get Conversation Details
334
+
335
+ ```typescript
336
+ // Get conversation with messages and participants
337
+ const conversation = await messenger.conversations.get('CONVERSATION_ID', {
338
+ fields: ['messages', 'participants'],
339
+ limit: 20
340
+ });
341
+
342
+ // Access participants
343
+ conversation.participants?.data.forEach(participant => {
344
+ console.log(`${participant.name || participant.username} (${participant.id})`);
345
+ });
346
+
347
+ // Access messages
348
+ conversation.messages?.data.forEach(msg => {
349
+ console.log(`Message ID: ${msg.id}, Created: ${msg.created_time}`);
350
+ });
351
+ ```
352
+
353
+ #### Get Message Details
354
+
355
+ ```typescript
356
+ // Get full message details
357
+ const message = await messenger.conversations.getMessage('MESSAGE_ID', {
358
+ fields: ['id', 'created_time', 'from', 'to', 'message', 'attachments', 'reactions', 'reply_to']
359
+ });
360
+
361
+ console.log(`From: ${message.from?.username}`);
362
+ console.log(`Text: ${message.message}`);
363
+
364
+ // Check attachments
365
+ if (message.attachments?.data) {
366
+ message.attachments.data.forEach(att => {
367
+ console.log(`Attachment: ${att.file_url || att.image_data?.url}`);
368
+ });
369
+ }
370
+
371
+ // Check reactions
372
+ if (message.reactions?.data) {
373
+ message.reactions.data.forEach(reaction => {
374
+ console.log(`${reaction.reaction} (${reaction.users.length} users)`);
375
+ });
376
+ }
377
+ ```
378
+
379
+ #### Get Recent Messages (Convenience Method)
380
+
381
+ ```typescript
382
+ // Get the 20 most recent messages with full details
383
+ const messages = await messenger.conversations.getRecentMessages('CONVERSATION_ID');
384
+
385
+ messages.forEach(msg => {
386
+ const sender = msg.from?.name || msg.from?.username;
387
+ const text = msg.message || '(attachment)';
388
+ console.log(`${sender}: ${text}`);
389
+ });
390
+ ```
391
+
392
+ #### Pagination
393
+
394
+ ```typescript
395
+ // Paginate through conversations
396
+ let after: string | undefined;
397
+ let hasMore = true;
398
+
399
+ while (hasMore) {
400
+ const conversations = await messenger.conversations.list('PAGE_ID', {
401
+ platform: 'messenger',
402
+ limit: 25,
403
+ after
404
+ });
405
+
406
+ // Process conversations
407
+ console.log(`Fetched ${conversations.data.length} conversations`);
408
+
409
+ // Check for next page
410
+ if (conversations.paging?.cursors?.after) {
411
+ after = conversations.paging.cursors.after;
412
+ } else {
413
+ hasMore = false;
414
+ }
415
+ }
416
+ ```
417
+
418
+ #### Important Limitations
419
+
420
+ - **20 Message Limit**: You can only retrieve full details for the **20 most recent messages** in a conversation. Older messages will return an error.
421
+ - **Pending Messages**: Conversations in the "pending" folder that are inactive for 30+ days are not returned.
422
+ - **Private Keys**: Accounts linked with private keys (email/phone) require Advanced Access approval to retrieve conversations.
423
+
424
+ ## Webhook Support
425
+
426
+ The SDK provides comprehensive webhook support with full TypeScript safety through discriminated unions.
427
+
428
+ ### Webhook Types and Processing
429
+
430
+ ```typescript
431
+ import {
432
+ processWebhookEvents,
433
+ extractWebhookEvents,
434
+ getWebhookEventType,
435
+ getWebhookPayloadEventTypes,
436
+ getPageWebhookEventTypes,
437
+ WebhookEventType,
438
+ GenericWebhookPayload,
439
+ PageWebhookPayload
440
+ } from '@warriorteam/messenger-sdk';
441
+
442
+ // Process webhook with type-safe handlers
443
+ app.post('/webhook', express.json(), async (req, res) => {
444
+ const payload: GenericWebhookPayload = req.body;
445
+
446
+ await processWebhookEvents(payload, {
447
+ onMessage: async (event) => {
448
+ // TypeScript knows this is MessageWebhookEvent
449
+ console.log(`Received message: ${event.message.text}`);
450
+ },
451
+ onMessageEdit: async (event) => {
452
+ // TypeScript knows this is MessageEditWebhookEvent
453
+ console.log(`Message edited to: ${event.message_edit.text}`);
454
+ },
455
+ onMessageReaction: async (event) => {
456
+ // TypeScript knows this is MessageReactionWebhookEvent
457
+ console.log(`Reaction: ${event.reaction.reaction}`);
458
+ },
459
+ onMessagingPostback: async (event) => {
460
+ // TypeScript knows this is MessagingPostbackWebhookEvent
461
+ console.log(`Postback: ${event.postback.payload}`);
462
+ }
463
+ });
464
+
465
+ res.sendStatus(200);
466
+ });
467
+ ```
468
+
469
+ ### Manual Event Processing
470
+
471
+ ```typescript
472
+ // Extract events manually
473
+ const events = extractWebhookEvents(payload);
474
+ for (const event of events) {
475
+ const eventType = getWebhookEventType(event);
476
+
477
+ switch (eventType) {
478
+ case WebhookEventType.MESSAGE:
479
+ // Handle message event
480
+ break;
481
+ case WebhookEventType.MESSAGE_EDIT:
482
+ // Handle edit event
483
+ break;
484
+ // ... other cases
485
+ }
486
+ }
487
+
488
+ // Check what event types are in the payload (Messenger events)
489
+ const eventTypes = getWebhookPayloadEventTypes(payload);
490
+ console.log('Received Messenger event types:', eventTypes); // e.g., ['message', 'postback']
491
+
492
+ // For Page webhooks, use getPageWebhookEventTypes instead:
493
+ // const pagePayload: PageWebhookPayload = req.body;
494
+ // const pageEventTypes = getPageWebhookEventTypes(pagePayload);
495
+ // console.log('Received Page event types:', pageEventTypes); // e.g., [WebhookEventType.FEED, WebhookEventType.VIDEOS]
496
+ ```
497
+
498
+ ### Webhook Verification
499
+
500
+ The SDK provides utilities for both subscription verification and signature validation:
501
+
502
+ ```typescript
503
+ import {
504
+ verifyWebhookSubscription,
505
+ verifyWebhookSignature
506
+ } from '@warriorteam/messenger-sdk';
507
+
508
+ // Subscription verification (GET request)
509
+ app.get('/webhook', (req, res) => {
510
+ const challenge = verifyWebhookSubscription(
511
+ req.query as any,
512
+ process.env.VERIFY_TOKEN!
513
+ );
514
+
515
+ if (challenge) {
516
+ res.send(challenge);
517
+ } else {
518
+ res.status(403).send('Forbidden');
519
+ }
520
+ });
521
+
522
+ // Signature verification (POST request)
523
+ app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
524
+ const signature = req.get('X-Hub-Signature-256');
525
+ const result = await verifyWebhookSignature(
526
+ req.body,
527
+ signature,
528
+ process.env.APP_SECRET!
529
+ );
530
+
531
+ if (!result.isValid) {
532
+ return res.status(401).json({error: result.error});
533
+ }
534
+
535
+ // Process webhook events...
536
+ const payload = JSON.parse(req.body.toString());
537
+ // ... handle events
538
+ });
539
+ ```
540
+
541
+ ### Type-Safe Event Handling
542
+
543
+ The SDK uses discriminated unions for perfect TypeScript support:
544
+
545
+ ```typescript
546
+ import {
547
+ MessengerWebhookEvent,
548
+ isMessageEvent,
549
+ isMessageEditEvent,
550
+ isMessagingPostbackEvent
551
+ } from '@warriorteam/messenger-sdk';
552
+
553
+ function handleWebhookEvent(event: MessengerWebhookEvent) {
554
+ if (isMessageEvent(event)) {
555
+ // TypeScript knows event.message exists
556
+ console.log(`Message: ${event.message.text}`);
557
+ } else if (isMessageEditEvent(event)) {
558
+ // TypeScript knows event.message_edit exists
559
+ console.log(`Edit: ${event.message_edit.text}`);
560
+ } else if (isMessagingPostbackEvent(event)) {
561
+ // TypeScript knows event.postback exists
562
+ console.log(`Postback: ${event.postback.payload}`);
563
+ }
564
+ }
565
+ ```
566
+
567
+ ### Supported Webhook Event Types
568
+
569
+ #### Messenger Platform Events
570
+ - `MESSAGE` - User sends a message
571
+ - `MESSAGE_EDIT` - User edits a sent message
572
+ - `MESSAGE_REACTION` - User reacts to a message
573
+ - `MESSAGE_READ` - User reads messages (read receipts)
574
+ - `MESSAGING_FEEDBACK` - User submits feedback via templates
575
+ - `MESSAGING_POSTBACK` - User clicks buttons/quick replies
576
+
577
+ #### Page Webhook Events
578
+ - `FEED` - Page feed changes (posts, photos, videos, status updates)
579
+ - `VIDEOS` - Video encoding status changes (processing, ready, error)
580
+ - `LIVE_VIDEOS` - Live video status changes (live, stopped, scheduled, VOD ready)
581
+
582
+ ### Page Webhook Type Helpers
583
+
584
+ The SDK provides specialized type guards and helpers for Page webhook events:
585
+
586
+ ```typescript
587
+ import {
588
+ isFeedEvent,
589
+ isVideoEvent,
590
+ isLiveVideoEvent,
591
+ isVideoProcessing,
592
+ isVideoReady,
593
+ isLiveVideoProcessing,
594
+ isLive,
595
+ extractVideoContext,
596
+ extractLiveVideoContext,
597
+ getPageWebhookEventTypes,
598
+ FeedActionVerb,
599
+ VideoStatus,
600
+ LiveVideoStatus,
601
+ PageWebhookPayload
602
+ } from '@warriorteam/messenger-sdk';
603
+
604
+ // Handle Page webhooks
605
+ app.post('/webhook/page', express.json(), async (req, res) => {
606
+ const payload: PageWebhookPayload = req.body;
607
+
608
+ // Check what types of Page events are in this payload
609
+ const eventTypes = getPageWebhookEventTypes(payload);
610
+ console.log('Received Page event types:', eventTypes); // e.g., [WebhookEventType.FEED, WebhookEventType.VIDEOS]
611
+
612
+ for (const entry of payload.entry) {
613
+ for (const change of entry.changes || []) {
614
+ // Feed events (posts, photos, videos, status)
615
+ if (isFeedEvent(change)) {
616
+ console.log(`Feed ${change.value.verb}: ${change.value.item} by ${change.value.from.id}`);
617
+ if (change.value.verb === FeedActionVerb.ADD) {
618
+ console.log('New post created!');
619
+ }
620
+ }
621
+
622
+ // Video encoding events
623
+ if (isVideoEvent(change)) {
624
+ const context = extractVideoContext(entry.id, entry.time, change);
625
+ if (context.isReady) {
626
+ console.log(`Video ${context.videoId} is ready!`);
627
+ } else if (context.isProcessing) {
628
+ console.log(`Video ${context.videoId} is still processing...`);
629
+ }
630
+ }
631
+
632
+ // Live video events
633
+ if (isLiveVideoEvent(change)) {
634
+ const context = extractLiveVideoContext(entry.id, entry.time, change);
635
+ if (context.isLive) {
636
+ console.log(`Live stream ${context.videoId} is now live!`);
637
+ } else if (context.isVODReady) {
638
+ console.log(`Live stream ${context.videoId} VOD is ready!`);
639
+ }
640
+ }
641
+ }
642
+ }
643
+
644
+ res.sendStatus(200);
645
+ });
646
+ ```
647
+
648
+ **Note**: Page webhooks use a different structure (`entry[].changes[]`) compared to Messenger webhooks (`entry[].messaging[]`). The SDK provides helpers for both.
649
+
650
+ ### Complete Controller Example (NestJS/Express)
651
+
652
+ Here's a complete TypeScript controller for handling webhooks with proper types:
653
+
654
+ ```typescript
655
+ import { Controller, Post, Body, Headers, Res, Get, Query } from '@nestjs/common';
656
+ import { Response } from 'express';
657
+ import {
658
+ GenericWebhookPayload,
659
+ PageWebhookPayload,
660
+ verifyWebhookSignature,
661
+ verifyWebhookSubscription,
662
+ processWebhookEvents,
663
+ isFeedEvent,
664
+ isVideoEvent,
665
+ isLiveVideoEvent,
666
+ } from '@warriorteam/messenger-sdk';
667
+
668
+ @Controller('webhook')
669
+ export class WebhookController {
670
+ // Subscription verification (GET)
671
+ @Get()
672
+ verifyWebhook(@Query() query: any, @Res() res: Response) {
673
+ const challenge = verifyWebhookSubscription(
674
+ query,
675
+ process.env.VERIFY_TOKEN!
676
+ );
677
+
678
+ if (challenge) {
679
+ return res.send(challenge);
680
+ }
681
+ return res.status(403).send('Forbidden');
682
+ }
683
+
684
+ // Messenger webhooks (POST)
685
+ @Post('messenger')
686
+ async handleMessenger(
687
+ @Body() payload: GenericWebhookPayload, // ← Correct type for Messenger
688
+ @Headers('x-hub-signature-256') signature: string,
689
+ @Res() res: Response
690
+ ) {
691
+ // Verify signature
692
+ const verification = await verifyWebhookSignature(
693
+ JSON.stringify(payload),
694
+ signature,
695
+ process.env.APP_SECRET!
696
+ );
697
+
698
+ if (!verification.isValid) {
699
+ return res.status(401).json({ error: 'Invalid signature' });
700
+ }
701
+
702
+ // Process events with type-safe handlers
703
+ await processWebhookEvents(payload, {
704
+ onMessage: async (event) => {
705
+ // event is MessageWebhookEvent - fully typed!
706
+ console.log(`From ${event.sender.id}: ${event.message.text}`);
707
+ },
708
+ onMessagingPostback: async (event) => {
709
+ // event is MessagingPostbackWebhookEvent
710
+ console.log(`Button clicked: ${event.postback.payload}`);
711
+ },
712
+ onMessageReaction: async (event) => {
713
+ // event is MessageReactionWebhookEvent
714
+ console.log(`Reaction: ${event.reaction.reaction}`);
715
+ },
716
+ });
717
+
718
+ return res.sendStatus(200);
719
+ }
720
+
721
+ // Page webhooks (POST)
722
+ @Post('page')
723
+ async handlePage(
724
+ @Body() payload: PageWebhookPayload, // ← Correct type for Page webhooks
725
+ @Headers('x-hub-signature-256') signature: string,
726
+ @Res() res: Response
727
+ ) {
728
+ // Verify signature
729
+ const verification = await verifyWebhookSignature(
730
+ JSON.stringify(payload),
731
+ signature,
732
+ process.env.APP_SECRET!
733
+ );
734
+
735
+ if (!verification.isValid) {
736
+ return res.status(401).json({ error: 'Invalid signature' });
737
+ }
738
+
739
+ // Process Page events
740
+ for (const entry of payload.entry) {
741
+ for (const change of entry.changes || []) {
742
+ if (isFeedEvent(change)) {
743
+ console.log(`Page feed: ${change.value.verb} ${change.value.item}`);
744
+ }
745
+
746
+ if (isVideoEvent(change)) {
747
+ console.log(`Video status: ${change.value.status.video_status}`);
748
+ }
749
+
750
+ if (isLiveVideoEvent(change)) {
751
+ console.log(`Live video status: ${change.value.status}`);
752
+ }
753
+ }
754
+ }
755
+
756
+ return res.sendStatus(200);
757
+ }
758
+ }
759
+ ```
760
+
761
+ **Key Types to Use:**
762
+ - `GenericWebhookPayload` - For Messenger Platform webhooks (`entry[].messaging[]`)
763
+ - `PageWebhookPayload` - For Page webhooks (`entry[].changes[]`)
764
+
765
+ ## Error Handling
766
+
767
+ The SDK provides specific error types for different scenarios:
768
+
769
+ ```typescript
770
+ import {
771
+ MessengerAPIError,
772
+ MessengerNetworkError,
773
+ MessengerTimeoutError,
774
+ MessengerConfigError
775
+ } from '@warriorteam/messenger-sdk';
776
+
777
+ try {
778
+ await messenger.send.message({
779
+ recipient: { id: 'USER_PSID' },
780
+ message: { text: 'Hello!' }
781
+ });
782
+ } catch (error) {
783
+ if (error instanceof MessengerAPIError) {
784
+ console.error('API Error:', error.message, error.code);
785
+ } else if (error instanceof MessengerNetworkError) {
786
+ console.error('Network Error:', error.message);
787
+ } else if (error instanceof MessengerTimeoutError) {
788
+ console.error('Timeout Error:', error.timeout);
789
+ }
790
+ }
791
+ ```
792
+
793
+ ## Examples
794
+
795
+ Check the `examples/` directory for complete usage examples:
796
+
797
+ - `send-message.ts` - Basic message sending
798
+ - `upload-attachment.ts` - Attachment handling
799
+ - `send-template.ts` - Template messages
800
+ - `user-profile.ts` - Profile API usage
801
+ - `moderation.ts` - User moderation
802
+ - `conversations.ts` - Retrieve conversations and messages
803
+ - `webhook-handler.ts` - Complete webhook setup with type safety
804
+ - `multi-tenant.ts` - Token override for multi-tenant applications
805
+ - `signature-verification.ts` - Webhook security implementation
806
+
807
+ ## Development
808
+
809
+ ```bash
810
+ # Install dependencies
811
+ npm install
812
+
813
+ # Build the project
814
+ npm run build
815
+
816
+ # Run tests
817
+ npm test
818
+
819
+ # Lint code
820
+ npm run lint
821
+
822
+ # Format code
823
+ npm run format
824
+ ```
825
+
826
+ ## Changelog
827
+
828
+ ### v1.5.2 (Latest)
829
+ - **Added**: `getPageWebhookEventTypes()` utility function for extracting event types from Page webhooks
830
+ - Returns `WebhookEventType[]` array with proper enum values (e.g., `[WebhookEventType.FEED, WebhookEventType.VIDEOS]`)
831
+ - Maps Page webhook `field` values to `WebhookEventType` enum for type safety
832
+ - Similar to `getWebhookPayloadEventTypes()` but for `PageWebhookPayload` structure
833
+ - Properly exported from main package
834
+ - **Improved**: Documentation with usage examples for Page webhook event type extraction
835
+
836
+ ### v1.5.1
837
+ - **Added**: Complete NestJS/Express webhook controller example in README
838
+ - **Updated**: WebhookEventType enum to include FEED, VIDEOS, LIVE_VIDEOS
839
+ - **Improved**: Documentation with clear type usage for webhook handlers and Page webhook utilities
840
+
841
+ ### v1.5.0
842
+ - **Added**: Conversations API for retrieving conversation history and messages
843
+ - List conversations for Messenger and Instagram
844
+ - Get conversation details with participants and messages
845
+ - Retrieve individual message details with attachments, reactions, and replies
846
+ - Convenience method for getting recent messages with full details
847
+ - Find conversations by user ID
848
+ - Full pagination support
849
+ - Support for both Messenger and Instagram platforms
850
+ - Token override support for multi-tenant applications
851
+ - **Added**: Comprehensive TypeScript types for Conversations API
852
+ - `Conversation`, `ConversationDetail`, `Message` types
853
+ - Message attachments, reactions, shares, story replies
854
+ - Image/video data types for media attachments
855
+ - Request/response types for all operations
856
+
857
+ ### v1.4.2
858
+ - **Fixed**: Resolved export naming conflicts for `isProcessing` function between video and live video webhooks
859
+ - `isProcessing` from videos module now exported as `isVideoProcessing`
860
+ - `isProcessing` from live-videos module now exported as `isLiveVideoProcessing`
861
+ - Added explicit exports for all video and live video helper functions with unique names
862
+ - Similar to existing pattern used for `ReferralType` and `ReferralSource` conflicts
863
+ - **Improved**: Better TypeScript type safety with no export ambiguities
864
+
865
+ ### v1.4.x
866
+ - **Added**: Comprehensive Facebook Page webhook types
867
+ - Feed webhook events (posts, photos, videos, status updates)
868
+ - Video encoding webhook events (processing, ready, error states)
869
+ - Live video webhook events (live, stopped, scheduled, VOD ready)
870
+ - **Added**: Type guards and helper functions for all Page webhook events
871
+ - **Added**: Context extraction utilities for video events
872
+
873
+ ### v1.3.x
874
+ - **Added**: Token override support for multi-tenant applications
875
+ - **Improved**: All API methods now accept optional `RequestOptions` parameter
876
+
877
+ ### v1.1.0
878
+ - **Added**: User Profile API support
879
+
880
+ ## License
881
+
882
+ MIT
883
+
884
+ ## Support
885
+
886
886
  For issues and questions, please visit our [GitHub repository](https://github.com/warriorteam/messenger-sdk).