@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 +885 -885
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js.map +1 -1
- package/package.json +62 -62
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).
|