wuzapi 1.4.0 → 1.5.1

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.
Files changed (42) hide show
  1. package/README.md +513 -1217
  2. package/dist/client.d.ts +2 -1
  3. package/dist/client.js +91 -0
  4. package/dist/client.js.map +1 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +12 -811
  7. package/dist/index.js.map +1 -1
  8. package/dist/modules/admin.d.ts +8 -0
  9. package/dist/modules/admin.js +37 -0
  10. package/dist/modules/admin.js.map +1 -0
  11. package/dist/modules/chat.d.ts +21 -1
  12. package/dist/modules/chat.js +173 -0
  13. package/dist/modules/chat.js.map +1 -0
  14. package/dist/modules/group.d.ts +25 -1
  15. package/dist/modules/group.js +142 -0
  16. package/dist/modules/group.js.map +1 -0
  17. package/dist/modules/newsletter.d.ts +9 -0
  18. package/dist/modules/newsletter.js +13 -0
  19. package/dist/modules/newsletter.js.map +1 -0
  20. package/dist/modules/session.d.ts +13 -1
  21. package/dist/modules/session.js +85 -0
  22. package/dist/modules/session.js.map +1 -0
  23. package/dist/modules/user.d.ts +5 -1
  24. package/dist/modules/user.js +40 -0
  25. package/dist/modules/user.js.map +1 -0
  26. package/dist/modules/webhook.d.ts +9 -1
  27. package/dist/modules/webhook.js +33 -0
  28. package/dist/modules/webhook.js.map +1 -0
  29. package/dist/types/chat.d.ts +55 -0
  30. package/dist/types/group.d.ts +52 -0
  31. package/dist/types/index.d.ts +1 -0
  32. package/dist/types/index.js +396 -0
  33. package/dist/types/index.js.map +1 -0
  34. package/dist/types/newsletter.d.ts +17 -0
  35. package/dist/types/session.d.ts +16 -0
  36. package/dist/types/user.d.ts +5 -0
  37. package/dist/types/webhook.d.ts +9 -0
  38. package/dist/wuzapi-client.d.ts +2 -0
  39. package/dist/wuzapi-client.js +45 -0
  40. package/dist/wuzapi-client.js.map +1 -0
  41. package/package.json +1 -1
  42. package/dist/vite-env.d.ts +0 -1
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  A comprehensive TypeScript client library for the [WuzAPI WhatsApp API](https://github.com/asternic/wuzapi). This library provides a simple and intuitive interface to interact with WhatsApp through the WuzAPI service.
4
4
 
5
- ## Features
5
+ ## 🚀 Features
6
6
 
7
- - 🔥 **Full TypeScript Support** - Complete type definitions for all API endpoints
7
+ - 🔥 **Full TypeScript Support** - Complete type definitions for all API endpoints
8
8
  - 🏗️ **Modular Architecture** - Organized by functionality (admin, session, chat, user, group, webhook)
9
9
  - 🚀 **Promise-based** - Modern async/await support
10
10
  - 🛡️ **Error Handling** - Comprehensive error handling with detailed error types
@@ -12,7 +12,7 @@ A comprehensive TypeScript client library for the [WuzAPI WhatsApp API](https://
12
12
  - 🔧 **Easy Configuration** - Simple setup with minimal configuration
13
13
  - 📖 **Well Documented** - Extensive documentation and examples
14
14
 
15
- ## Installation
15
+ ## 📦 Installation
16
16
 
17
17
  ```bash
18
18
  npm install wuzapi
@@ -24,9 +24,9 @@ or
24
24
  yarn add wuzapi
25
25
  ```
26
26
 
27
- ## Quick Start
27
+ ## Quick Start
28
28
 
29
- ### Traditional Usage (Global Token)
29
+ ### Basic Setup
30
30
 
31
31
  ```typescript
32
32
  import WuzapiClient from "wuzapi";
@@ -42,116 +42,225 @@ await client.session.connect({
42
42
  Immediate: false,
43
43
  });
44
44
 
45
- // Send a text message
45
+ // Send a message
46
46
  await client.chat.sendText({
47
47
  Phone: "5491155554444",
48
- Body: "Hello from WuzAPI!",
48
+ Body: "Hello from WuzAPI! 🎉",
49
49
  });
50
+ ```
50
51
 
51
- // Get session status
52
- const status = await client.session.getStatus();
53
- console.log("Connected:", status.Connected);
54
- console.log("Logged In:", status.LoggedIn);
52
+ ### Login Options
53
+
54
+ #### Option 1: QR Code (Traditional)
55
+ ```typescript
56
+ // Get QR code for scanning
57
+ const qr = await client.session.getQRCode();
58
+ console.log("Scan this QR code:", qr.QRCode);
59
+ ```
60
+
61
+ #### Option 2: Phone Pairing (New!)
62
+ ```typescript
63
+ // Pair using verification code (SMS/Call)
64
+ await client.session.pairPhone("5491155554444", "123456");
55
65
  ```
56
66
 
57
- ### Flexible Token Usage
67
+ ## 🔧 Configuration
58
68
 
59
69
  ```typescript
60
- import WuzapiClient from "wuzapi";
70
+ interface WuzapiConfig {
71
+ apiUrl: string; // Your WuzAPI server URL
72
+ token?: string; // Authentication token (can be provided per request)
73
+ }
61
74
 
62
- // Create client without token (or with admin token)
75
+ // Global token approach
63
76
  const client = new WuzapiClient({
64
77
  apiUrl: "http://localhost:8080",
65
- token: "admin-token", // Optional: admin token as default
78
+ token: "your-token",
66
79
  });
67
80
 
68
- // Use specific user token for operations
69
- const userToken = "user-specific-token";
70
-
71
- // Connect with user token
72
- await client.session.connect(
73
- {
74
- Subscribe: ["Message", "ReadReceipt"],
75
- Immediate: false,
76
- },
77
- { token: userToken }
78
- );
81
+ // Flexible token approach
82
+ const client = new WuzapiClient({
83
+ apiUrl: "http://localhost:8080",
84
+ });
79
85
 
80
- // Send message with user token
86
+ // Use different tokens for different operations
81
87
  await client.chat.sendText(
82
- {
83
- Phone: "5491155554444",
84
- Body: "Hello from user-specific token!",
85
- },
86
- { token: userToken }
88
+ { Phone: "123", Body: "Hello" },
89
+ { token: "user-specific-token" }
87
90
  );
91
+ ```
92
+
93
+ ## 💬 Essential Chat Operations
94
+
95
+ ```typescript
96
+ // Send text message
97
+ await client.chat.sendText({
98
+ Phone: "5491155554444",
99
+ Body: "Hello World!",
100
+ });
101
+
102
+ // Send image
103
+ await client.chat.sendImage({
104
+ Phone: "5491155554444",
105
+ Image: "data:image/jpeg;base64,/9j/4AAQ...",
106
+ Caption: "Check this out!",
107
+ });
108
+
109
+ // Send interactive buttons
110
+ await client.chat.sendButtons({
111
+ Phone: "5491155554444",
112
+ Body: "Choose an option:",
113
+ Buttons: [
114
+ { ButtonId: "yes", ButtonText: { DisplayText: "Yes" }, Type: 1 },
115
+ { ButtonId: "no", ButtonText: { DisplayText: "No" }, Type: 1 },
116
+ ],
117
+ });
118
+
119
+ // Send list menu
120
+ await client.chat.sendList({
121
+ Phone: "5491155554444",
122
+ Body: "Select from menu:",
123
+ Title: "Options",
124
+ ButtonText: "View Menu",
125
+ Sections: [{
126
+ Title: "Main Options",
127
+ Rows: [
128
+ { Title: "Option 1", Description: "First choice", RowId: "opt1" },
129
+ { Title: "Option 2", Description: "Second choice", RowId: "opt2" },
130
+ ],
131
+ }],
132
+ });
133
+
134
+ // Send poll
135
+ await client.chat.sendPoll({
136
+ Phone: "5491155554444",
137
+ Name: "What's your favorite color?",
138
+ Options: [{ Name: "Red" }, { Name: "Blue" }, { Name: "Green" }],
139
+ SelectableCount: 1,
140
+ });
141
+ ```
142
+
143
+ ## 👥 Group Management
144
+
145
+ ```typescript
146
+ // Create group
147
+ const group = await client.group.create("My Group", [
148
+ "5491155553934",
149
+ "5491155553935",
150
+ ]);
88
151
 
89
- // Admin operations with admin token (uses default)
90
- const users = await client.admin.listUsers();
152
+ // Get group info
153
+ const info = await client.group.getInfo(group.JID);
91
154
 
92
- // Or explicitly use admin token
93
- const newUser = await client.admin.addUser(
94
- { name: "John", token: "new-user-token" },
95
- { token: "admin-token" }
155
+ // Add participants
156
+ await client.group.updateParticipants(
157
+ group.JID,
158
+ "add",
159
+ ["5491155553936"]
96
160
  );
161
+
162
+ // Set group settings
163
+ await client.group.setName(group.JID, "New Group Name");
164
+ await client.group.setTopic(group.JID, "Welcome message");
165
+ await client.group.setAnnounce(group.JID, true); // Only admins can send
97
166
  ```
98
167
 
99
- ## Configuration
168
+ ## 👤 User Operations
169
+
170
+ ```typescript
171
+ // Check if numbers are WhatsApp users
172
+ const check = await client.user.check(["5491155554444"]);
173
+
174
+ // Get user info
175
+ const info = await client.user.getInfo(["5491155554444"]);
176
+
177
+ // Get contacts
178
+ const contacts = await client.user.getContacts();
179
+
180
+ // Send presence status
181
+ await client.user.sendPresence({
182
+ Phone: "5491155554444",
183
+ State: "available",
184
+ });
185
+ ```
100
186
 
101
- The client requires a configuration object with the following properties:
187
+ ## 🔗 Webhook Setup
102
188
 
103
189
  ```typescript
104
- interface WuzapiConfig {
105
- apiUrl: string; // The WuzAPI server URL
106
- token?: string; // Your user authentication token (optional)
107
- }
190
+ // Set webhook URL
191
+ await client.webhook.setWebhook("https://your-server.com/webhook");
108
192
 
109
- interface RequestOptions {
110
- token?: string; // Token override for specific requests
111
- }
193
+ // Get webhook config
194
+ const config = await client.webhook.getWebhook();
195
+
196
+ // Update webhook
197
+ await client.webhook.updateWebhook("https://new-server.com/webhook");
112
198
  ```
113
199
 
114
- ### Authentication Options
200
+ ## 📚 Examples
201
+
202
+ Check out the complete examples in the `examples/` directory:
203
+
204
+ - **[basic-usage.js](examples/basic-usage.js)** - Getting started, connection, basic operations
205
+ - **[advanced-features.js](examples/advanced-features.js)** - Phone pairing, interactive messages, advanced group management
206
+ - **[chatbot-example.js](examples/chatbot-example.js)** - Complete bot with commands and auto-replies
207
+ - **[webhook-events-example.js](examples/webhook-events-example.js)** - Comprehensive webhook event handling
115
208
 
116
- You have two ways to handle authentication:
209
+ ### Run Examples
117
210
 
118
- 1. **Global Token**: Set a default token in the client configuration
119
- 2. **Per-Request Token**: Override the token for specific API calls
211
+ ```bash
212
+ # Basic usage
213
+ node examples/basic-usage.js
214
+
215
+ # Advanced features
216
+ node examples/advanced-features.js
217
+
218
+ # Start chatbot
219
+ node examples/chatbot-example.js
220
+ ```
221
+
222
+ ## 🤖 Simple Bot Example
120
223
 
121
224
  ```typescript
122
- // Option 1: Global token (traditional usage)
123
- const client = new WuzapiClient({
124
- apiUrl: "http://localhost:8080",
125
- token: "your-default-token",
126
- });
225
+ import WuzapiClient from "wuzapi";
127
226
 
128
- // Option 2: No global token, specify per request
129
227
  const client = new WuzapiClient({
130
228
  apiUrl: "http://localhost:8080",
131
- // token is optional
229
+ token: "your-token",
132
230
  });
133
231
 
134
- // Option 3: Global token with per-request overrides
135
- const client = new WuzapiClient({
136
- apiUrl: "http://localhost:8080",
137
- token: "admin-token", // Default admin token
138
- });
232
+ // Connect and wait for messages
233
+ await client.session.connect({ Subscribe: ["Message"] });
234
+ await client.webhook.setWebhook("https://your-server.com/webhook");
139
235
 
140
- // Use different token for specific operations
141
- await client.chat.sendText(
142
- { Phone: "5491155554444", Body: "Hello!" },
143
- { token: "user-specific-token" }
144
- );
236
+ // In your webhook handler:
237
+ app.post("/webhook", async (req, res) => {
238
+ const { event } = req.body;
239
+
240
+ if (event?.Message?.conversation) {
241
+ const message = event.Message.conversation;
242
+ const from = event.Info.RemoteJid.replace("@s.whatsapp.net", "");
243
+
244
+ if (message.toLowerCase().includes("hello")) {
245
+ await client.chat.sendText({
246
+ Phone: from,
247
+ Body: "Hello! 👋 How can I help you?",
248
+ });
249
+ }
250
+ }
251
+
252
+ res.json({ success: true });
253
+ });
145
254
  ```
146
255
 
147
- ## API Modules
148
-
149
- The client is organized into logical modules:
256
+ ---
150
257
 
151
- ### Session Module
258
+ ## 📖 Complete API Reference
152
259
 
153
- Manage WhatsApp connection and session state.
260
+ <details>
261
+ <summary><strong>📱 Session Module</strong> - Connection and authentication</summary>
154
262
 
263
+ ### Connection
155
264
  ```typescript
156
265
  // Connect to WhatsApp
157
266
  await client.session.connect({
@@ -162,15 +271,30 @@ await client.session.connect({
162
271
  // Get connection status
163
272
  const status = await client.session.getStatus();
164
273
 
165
- // Get QR code for initial setup
166
- const qr = await client.session.getQRCode();
167
-
168
274
  // Disconnect (keeps session)
169
275
  await client.session.disconnect();
170
276
 
171
277
  // Logout (destroys session)
172
278
  await client.session.logout();
279
+ ```
280
+
281
+ ### Authentication
282
+ ```typescript
283
+ // Get QR code for scanning
284
+ const qr = await client.session.getQRCode();
285
+
286
+ // Pair phone using verification code (alternative to QR)
287
+ await client.session.pairPhone("5491155554444", "123456");
288
+
289
+ // Request message history sync
290
+ await client.session.requestHistory();
291
+
292
+ // Configure proxy
293
+ await client.session.setProxy("socks5://user:pass@proxy:port");
294
+ ```
173
295
 
296
+ ### S3 Storage
297
+ ```typescript
174
298
  // Configure S3 storage
175
299
  await client.session.configureS3({
176
300
  enabled: true,
@@ -183,29 +307,31 @@ await client.session.configureS3({
183
307
  mediaDelivery: "both",
184
308
  retentionDays: 30,
185
309
  });
310
+
311
+ // Get S3 configuration
312
+ const s3Config = await client.session.getS3Config();
313
+
314
+ // Test S3 connection
315
+ await client.session.testS3();
316
+
317
+ // Delete S3 configuration
318
+ await client.session.deleteS3Config();
186
319
  ```
187
320
 
188
- ### Chat Module
321
+ </details>
189
322
 
190
- Send messages and manage chat interactions.
323
+ <details>
324
+ <summary><strong>💬 Chat Module</strong> - Send and manage messages</summary>
191
325
 
326
+ ### Basic Messages
192
327
  ```typescript
193
- // Send text message (using global token)
328
+ // Send text message
194
329
  await client.chat.sendText({
195
330
  Phone: "5491155554444",
196
331
  Body: "Hello World!",
197
332
  Id: "optional-message-id",
198
333
  });
199
334
 
200
- // Send with specific token override
201
- await client.chat.sendText(
202
- {
203
- Phone: "5491155554444",
204
- Body: "Hello with custom token!",
205
- },
206
- { token: "user-specific-token" }
207
- );
208
-
209
335
  // Reply to a message
210
336
  await client.chat.sendText({
211
337
  Phone: "5491155554444",
@@ -215,47 +341,97 @@ await client.chat.sendText({
215
341
  Participant: "5491155553935@s.whatsapp.net",
216
342
  },
217
343
  });
344
+ ```
218
345
 
346
+ ### Media Messages
347
+ ```typescript
219
348
  // Send image
220
349
  await client.chat.sendImage({
221
350
  Phone: "5491155554444",
222
- Image: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...",
351
+ Image: "data:image/jpeg;base64,/9j/4AAQ...",
223
352
  Caption: "Check this out!",
224
353
  });
225
354
 
226
- // Send template message with buttons
227
- await client.chat.sendTemplate({
355
+ // Send audio
356
+ await client.chat.sendAudio({
357
+ Phone: "5491155554444",
358
+ Audio: "data:audio/ogg;base64,T2dnUw...",
359
+ });
360
+
361
+ // Send video
362
+ await client.chat.sendVideo({
363
+ Phone: "5491155554444",
364
+ Video: "data:video/mp4;base64,AAAAIGZ0eXA...",
365
+ Caption: "Video caption",
366
+ });
367
+
368
+ // Send document
369
+ await client.chat.sendDocument({
370
+ Phone: "5491155554444",
371
+ Document: "data:application/pdf;base64,JVBERi0x...",
372
+ FileName: "document.pdf",
373
+ });
374
+
375
+ // Send sticker
376
+ await client.chat.sendSticker({
377
+ Phone: "5491155554444",
378
+ Sticker: "data:image/webp;base64,UklGRuI...",
379
+ });
380
+ ```
381
+
382
+ ### Interactive Messages
383
+ ```typescript
384
+ // Send buttons
385
+ await client.chat.sendButtons({
228
386
  Phone: "5491155554444",
229
- Content: "Choose an option:",
230
- Footer: "Powered by WuzAPI",
387
+ Body: "Choose an option:",
388
+ Footer: "Select one:",
231
389
  Buttons: [
232
- { DisplayText: "Yes", Type: "quickreply" },
233
- { DisplayText: "No", Type: "quickreply" },
234
- { DisplayText: "Visit Site", Type: "url", Url: "https://example.com" },
235
- { DisplayText: "Call Us", Type: "call", PhoneNumber: "1155554444" },
390
+ { ButtonId: "option1", ButtonText: { DisplayText: "Option 1" }, Type: 1 },
391
+ { ButtonId: "option2", ButtonText: { DisplayText: "Option 2" }, Type: 1 },
236
392
  ],
237
393
  });
238
394
 
239
- // Send location
240
- await client.chat.sendLocation({
395
+ // Send list message
396
+ await client.chat.sendList({
241
397
  Phone: "5491155554444",
242
- Latitude: 48.85837,
243
- Longitude: 2.294481,
244
- Name: "Eiffel Tower, Paris",
398
+ Body: "Please select from the menu:",
399
+ Title: "Menu Options",
400
+ ButtonText: "View Menu",
401
+ Sections: [
402
+ {
403
+ Title: "Main Course",
404
+ Rows: [
405
+ { Title: "Pizza", Description: "Delicious pizza", RowId: "pizza" },
406
+ { Title: "Burger", Description: "Tasty burger", RowId: "burger" },
407
+ ],
408
+ },
409
+ ],
245
410
  });
246
411
 
247
- // Send contact
248
- await client.chat.sendContact({
412
+ // Send poll
413
+ await client.chat.sendPoll({
249
414
  Phone: "5491155554444",
250
- Name: "John Doe",
251
- Vcard:
252
- "BEGIN:VCARD\nVERSION:3.0\nN:Doe;John;;;\nFN:John Doe\nTEL:+1234567890\nEND:VCARD",
415
+ Name: "What's your favorite color?",
416
+ Options: [{ Name: "Red" }, { Name: "Blue" }, { Name: "Green" }],
417
+ SelectableCount: 1,
253
418
  });
419
+ ```
254
420
 
255
- // Mark messages as read
256
- await client.chat.markRead({
257
- Id: ["message-id-1", "message-id-2"],
258
- Chat: "5491155553934@s.whatsapp.net",
421
+ ### Message Management
422
+ ```typescript
423
+ // Delete a message
424
+ await client.chat.deleteMessage({
425
+ Phone: "5491155554444",
426
+ Id: "message-id-to-delete",
427
+ Remote: true, // Delete for everyone
428
+ });
429
+
430
+ // Edit a message
431
+ await client.chat.editMessage({
432
+ Phone: "5491155554444",
433
+ MessageId: "message-id-to-edit",
434
+ NewText: "This is the updated message text",
259
435
  });
260
436
 
261
437
  // React to message
@@ -265,6 +441,40 @@ await client.chat.react({
265
441
  Id: "message-id-to-react-to",
266
442
  });
267
443
 
444
+ // Mark messages as read
445
+ await client.chat.markRead({
446
+ Id: ["message-id-1", "message-id-2"],
447
+ Chat: "5491155553934@s.whatsapp.net",
448
+ });
449
+
450
+ // Send chat presence (typing indicator)
451
+ await client.chat.sendPresence({
452
+ Phone: "5491155554444",
453
+ State: "composing", // or "paused"
454
+ Media: "text",
455
+ });
456
+ ```
457
+
458
+ ### Location and Contacts
459
+ ```typescript
460
+ // Send location
461
+ await client.chat.sendLocation({
462
+ Phone: "5491155554444",
463
+ Latitude: 48.85837,
464
+ Longitude: 2.294481,
465
+ Name: "Eiffel Tower, Paris",
466
+ });
467
+
468
+ // Send contact
469
+ await client.chat.sendContact({
470
+ Phone: "5491155554444",
471
+ Name: "John Doe",
472
+ Vcard: "BEGIN:VCARD\nVERSION:3.0\nN:Doe;John;;;\nFN:John Doe\nTEL:+1234567890\nEND:VCARD",
473
+ });
474
+ ```
475
+
476
+ ### Media Download
477
+ ```typescript
268
478
  // Download media
269
479
  const media = await client.chat.downloadImage({
270
480
  Url: "https://mmg.whatsapp.net/d/f/...",
@@ -275,9 +485,10 @@ const media = await client.chat.downloadImage({
275
485
  });
276
486
  ```
277
487
 
278
- ### User Module
488
+ </details>
279
489
 
280
- Get information about WhatsApp users.
490
+ <details>
491
+ <summary><strong>👤 User Module</strong> - User information and contacts</summary>
281
492
 
282
493
  ```typescript
283
494
  // Check if numbers are WhatsApp users
@@ -291,12 +502,21 @@ const avatar = await client.user.getAvatar("5491155554444", true); // true for p
291
502
 
292
503
  // Get all contacts
293
504
  const contacts = await client.user.getContacts();
505
+
506
+ // Send user presence (online/offline status)
507
+ await client.user.sendPresence({
508
+ Phone: "5491155554444",
509
+ State: "available", // or "unavailable"
510
+ LastSeen: Date.now(),
511
+ });
294
512
  ```
295
513
 
296
- ### Group Module
514
+ </details>
297
515
 
298
- Manage WhatsApp groups.
516
+ <details>
517
+ <summary><strong>👥 Group Module</strong> - Group management</summary>
299
518
 
519
+ ### Basic Group Operations
300
520
  ```typescript
301
521
  // List all groups
302
522
  const groups = await client.group.list();
@@ -310,84 +530,108 @@ const newGroup = await client.group.create("My New Group", [
310
530
  // Get group info
311
531
  const groupInfo = await client.group.getInfo("120362023605733675@g.us");
312
532
 
533
+ // Leave a group
534
+ await client.group.leave("120362023605733675@g.us");
535
+ ```
536
+
537
+ ### Group Settings
538
+ ```typescript
313
539
  // Set group name
314
540
  await client.group.setName("120362023605733675@g.us", "New Group Name");
315
541
 
316
- // Set group photo (JPEG only)
317
- await client.group.setPhoto(
542
+ // Set group topic/description
543
+ await client.group.setTopic(
318
544
  "120362023605733675@g.us",
319
- "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
545
+ "Welcome to our group! Please read the rules."
320
546
  );
321
547
 
322
- // Get invite link
323
- const invite = await client.group.getInviteLink("120362023605733675@g.us");
548
+ // Set group announcement setting (only admins can send messages)
549
+ await client.group.setAnnounce("120362023605733675@g.us", true);
324
550
 
325
551
  // Set group locked (only admins can modify info)
326
552
  await client.group.setLocked("120362023605733675@g.us", true);
327
553
 
328
554
  // Set disappearing messages
329
555
  await client.group.setEphemeral("120362023605733675@g.us", "24h"); // '24h', '7d', '90d', or 'off'
556
+ ```
557
+
558
+ ### Group Media
559
+ ```typescript
560
+ // Set group photo (JPEG only)
561
+ await client.group.setPhoto(
562
+ "120362023605733675@g.us",
563
+ "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
564
+ );
330
565
 
331
566
  // Remove group photo
332
567
  await client.group.removePhoto("120362023605733675@g.us");
333
568
  ```
334
569
 
335
- ### Admin Module
570
+ ### Invites and Participants
571
+ ```typescript
572
+ // Get invite link
573
+ const invite = await client.group.getInviteLink("120362023605733675@g.us");
336
574
 
337
- Manage users (requires admin token).
575
+ // Join a group using invite link
576
+ const joinResult = await client.group.join("https://chat.whatsapp.com/XXXXXXXXX");
338
577
 
339
- ```typescript
340
- // Option 1: Create dedicated admin client
341
- const adminClient = new WuzapiClient({
342
- apiUrl: "http://localhost:8080",
343
- token: "your-admin-token",
344
- });
578
+ // Get group invite information
579
+ const inviteInfo = await client.group.getInviteInfo("https://chat.whatsapp.com/XXXXXXXXX");
345
580
 
346
- // List all users
347
- const users = await adminClient.admin.listUsers();
581
+ // Update group participants (add, remove, promote, demote)
582
+ await client.group.updateParticipants(
583
+ "120362023605733675@g.us",
584
+ "add", // "add", "remove", "promote", "demote"
585
+ ["5491155553936", "5491155553937"]
586
+ );
587
+ ```
348
588
 
349
- // Option 2: Use shared client with token override
350
- const client = new WuzapiClient({
351
- apiUrl: "http://localhost:8080",
352
- // No default token or user token as default
353
- });
589
+ </details>
354
590
 
355
- // Admin operations with explicit admin token
591
+ <details>
592
+ <summary><strong>👨‍💼 Admin Module</strong> - User management (requires admin token)</summary>
593
+
594
+ ```typescript
595
+ // List all users
356
596
  const users = await client.admin.listUsers({ token: "admin-token" });
357
597
 
598
+ // Get a specific user by ID
599
+ const user = await client.admin.getUser(2, { token: "admin-token" });
600
+
358
601
  // Add new user
359
- const newUser = await client.admin.addUser(
360
- {
361
- name: "John Doe",
362
- token: "user-token-123",
363
- webhook: "https://example.com/webhook",
364
- events: "Message,ReadReceipt",
365
- proxyConfig: {
366
- enabled: true,
367
- proxyURL: "socks5://user:pass@proxy:port",
368
- },
369
- s3Config: {
370
- enabled: true,
371
- endpoint: "https://s3.amazonaws.com",
372
- region: "us-east-1",
373
- bucket: "user-media-bucket",
374
- accessKey: "AKIA...",
375
- secretKey: "secret...",
376
- pathStyle: false,
377
- mediaDelivery: "both",
378
- retentionDays: 30,
379
- },
602
+ const newUser = await client.admin.addUser({
603
+ name: "John Doe",
604
+ token: "user-token-123",
605
+ webhook: "https://example.com/webhook",
606
+ events: "Message,ReadReceipt",
607
+ proxyConfig: {
608
+ enabled: true,
609
+ proxyURL: "socks5://user:pass@proxy:port",
380
610
  },
381
- { token: "admin-token" }
382
- );
611
+ s3Config: {
612
+ enabled: true,
613
+ endpoint: "https://s3.amazonaws.com",
614
+ region: "us-east-1",
615
+ bucket: "user-media-bucket",
616
+ accessKey: "AKIA...",
617
+ secretKey: "secret...",
618
+ pathStyle: false,
619
+ mediaDelivery: "both",
620
+ retentionDays: 30,
621
+ },
622
+ }, { token: "admin-token" });
383
623
 
384
624
  // Delete user
385
625
  await client.admin.deleteUser(2, { token: "admin-token" });
626
+
627
+ // Delete user completely (full deletion including all data)
628
+ await client.admin.deleteUserComplete(2, { token: "admin-token" });
386
629
  ```
387
630
 
388
- ### Webhook Module
631
+ </details>
389
632
 
390
- Configure webhook settings and handle incoming WhatsApp events.
633
+ <details>
634
+ <summary><strong>🔗 Webhook Module</strong> - Webhook configuration</summary>
391
635
 
392
636
  ```typescript
393
637
  // Set webhook URL
@@ -397,134 +641,44 @@ await client.webhook.setWebhook("https://my-server.com/webhook");
397
641
  const webhookConfig = await client.webhook.getWebhook();
398
642
  console.log("Webhook URL:", webhookConfig.webhook);
399
643
  console.log("Subscribed events:", webhookConfig.subscribe);
400
- ```
401
644
 
402
- ## Webhook Event Handling
645
+ // Update webhook URL
646
+ await client.webhook.updateWebhook("https://my-new-server.com/webhook");
403
647
 
404
- WuzAPI sends real-time events to your webhook endpoint. Here's how to handle different types of events:
405
-
406
- ## Webhook Payload Structure
648
+ // Delete webhook configuration
649
+ await client.webhook.deleteWebhook();
650
+ ```
407
651
 
408
- When S3 is enabled, webhook payloads will include S3 information based on the `media_delivery` setting configured in your WuzAPI instance.
652
+ </details>
409
653
 
410
- ### Standard Event Payload
654
+ <details>
655
+ <summary><strong>📰 Newsletter Module</strong> - Newsletter management (Business accounts only)</summary>
411
656
 
412
- ```json
413
- {
414
- "event": {
415
- "Info": {
416
- "ID": "3EB06F9067F80BAB89FF",
417
- "Type": "text",
418
- "PushName": "John Doe",
419
- "Timestamp": "2024-12-25T10:30:00Z",
420
- "Source": {
421
- "Chat": "5491155553934@s.whatsapp.net",
422
- "Sender": "5491155553934@s.whatsapp.net",
423
- "IsFromMe": false,
424
- "IsGroup": false
425
- }
426
- },
427
- "Message": {
428
- "conversation": "Hello, this is a test message!"
429
- },
430
- "IsEphemeral": false,
431
- "IsViewOnce": false,
432
- "IsDocumentWithCaption": false,
433
- "IsEdit": false
434
- }
435
- }
657
+ ```typescript
658
+ // List all subscribed newsletters
659
+ const newsletters = await client.newsletter.list();
660
+
661
+ newsletters.Newsletters.forEach((newsletter) => {
662
+ console.log(`Newsletter: ${newsletter.Name}`);
663
+ console.log(`Description: ${newsletter.Description}`);
664
+ console.log(`Handle: ${newsletter.Handle}`);
665
+ console.log(`State: ${newsletter.State}`);
666
+ });
436
667
  ```
437
668
 
438
- ### S3 Only (`media_delivery: "s3"`)
439
-
440
- ```json
441
- {
442
- "event": {
443
- "Info": {
444
- "ID": "3EB06F9067F80BAB89FF",
445
- "Type": "image",
446
- "PushName": "John Doe",
447
- "Timestamp": "2024-12-25T10:30:00Z",
448
- "Source": {
449
- "Chat": "5491155553934@s.whatsapp.net",
450
- "Sender": "5491155553934@s.whatsapp.net",
451
- "IsFromMe": false,
452
- "IsGroup": false
453
- }
454
- },
455
- "Message": {
456
- "imageMessage": {
457
- "caption": "Check out this photo!",
458
- "mimetype": "image/jpeg",
459
- "width": 1920,
460
- "height": 1080,
461
- "fileLength": 245632
462
- }
463
- },
464
- "IsEphemeral": false,
465
- "IsViewOnce": false
466
- },
467
- "s3": {
468
- "url": "https://my-bucket.s3.us-east-1.amazonaws.com/users/abc123/inbox/5491155553934/2024/12/25/images/3EB06F9067F80BAB89FF.jpg",
469
- "key": "users/abc123/inbox/5491155553934/2024/12/25/images/3EB06F9067F80BAB89FF.jpg",
470
- "bucket": "my-bucket",
471
- "size": 245632,
472
- "mimeType": "image/jpeg",
473
- "fileName": "3EB06F9067F80BAB89FF.jpg"
474
- }
475
- }
476
- ```
669
+ </details>
477
670
 
478
- ### Both S3 and Base64 (`media_delivery: "both"`)
479
-
480
- ```json
481
- {
482
- "event": {
483
- "Info": { "..." },
484
- "Message": {
485
- "imageMessage": {
486
- "caption": "Check out this photo!",
487
- "mimetype": "image/jpeg",
488
- "width": 1920,
489
- "height": 1080
490
- }
491
- }
492
- },
493
- "base64": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k=",
494
- "mimeType": "image/jpeg",
495
- "fileName": "3EB06F9067F80BAB89FF.jpg",
496
- "s3": {
497
- "url": "https://my-bucket.s3.us-east-1.amazonaws.com/users/abc123/inbox/5491155553934/2024/12/25/images/3EB06F9067F80BAB89FF.jpg",
498
- "key": "users/abc123/inbox/5491155553934/2024/12/25/images/3EB06F9067F80BAB89FF.jpg",
499
- "bucket": "my-bucket",
500
- "size": 245632,
501
- "mimeType": "image/jpeg",
502
- "fileName": "3EB06F9067F80BAB89FF.jpg"
503
- }
504
- }
505
- ```
671
+ ---
506
672
 
507
- ### Base64 Only (`media_delivery: "base64"`)
673
+ ## 🎣 Webhook Event Handling
508
674
 
509
- ```json
510
- {
511
- "event": { "..." },
512
- "base64": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k=",
513
- "mimeType": "image/jpeg",
514
- "fileName": "3EB06F9067F80BAB89FF.jpg"
515
- }
516
- ```
675
+ WuzAPI sends real-time events to your webhook endpoint. Here's how to handle them:
517
676
 
518
677
  ### Basic Webhook Setup
519
678
 
520
679
  ```typescript
521
680
  import express from "express";
522
- import WuzapiClient, {
523
- EventType,
524
- hasS3Media,
525
- hasBase64Media,
526
- WebhookPayload,
527
- } from "wuzapi";
681
+ import WuzapiClient, { getMessageContent, hasS3Media } from "wuzapi";
528
682
 
529
683
  const app = express();
530
684
  app.use(express.json());
@@ -534,787 +688,73 @@ const client = new WuzapiClient({
534
688
  token: "your-token",
535
689
  });
536
690
 
537
- // Webhook endpoint with S3 media support
538
691
  app.post("/webhook", async (req, res) => {
539
692
  try {
540
- const webhookPayload: WebhookPayload = req.body;
541
-
542
- // Handle S3 media information if present
693
+ const webhookPayload = req.body;
694
+
695
+ // Handle S3 media if present
543
696
  if (hasS3Media(webhookPayload)) {
544
- console.log("☁️ S3 Media:", {
545
- url: webhookPayload.s3.url,
546
- bucket: webhookPayload.s3.bucket,
547
- size: webhookPayload.s3.size,
548
- type: webhookPayload.s3.mimeType,
549
- });
550
- }
551
-
552
- if (hasBase64Media(webhookPayload)) {
553
- console.log("📦 Base64 media included");
554
- // Process base64 media: webhookPayload.base64
697
+ console.log("S3 Media:", webhookPayload.s3.url);
555
698
  }
556
-
557
- // Extract the actual event data
699
+
558
700
  const event = webhookPayload.event || webhookPayload;
559
- const eventType = detectEventType(event);
560
-
561
- // Handle different event types using EventType enum
562
- switch (eventType) {
563
- case EventType.MESSAGE:
564
- await handleMessage(event);
565
- break;
566
- case EventType.RECEIPT:
567
- await handleReceipt(event);
568
- break;
569
- case EventType.PRESENCE:
570
- await handlePresence(event);
571
- break;
572
- default:
573
- console.log(`Unhandled event type: ${eventType}`);
574
- }
575
-
576
- res.status(200).json({ success: true });
577
- } catch (error) {
578
- console.error("Webhook error:", error);
579
- res.status(500).json({ error: error.message });
580
- }
581
- });
582
-
583
- // Helper function to detect event type
584
- function detectEventType(event: any): EventType | string {
585
- if (event.Message && event.Info) return EventType.MESSAGE;
586
- if (event.MessageIDs && event.Type) return EventType.RECEIPT;
587
- if (event.From && typeof event.Unavailable !== "undefined")
588
- return EventType.PRESENCE;
589
- if (event.State && event.MessageSource) return EventType.CHAT_PRESENCE;
590
- if (event.Codes) return EventType.QR;
591
- // Add more detection logic as needed
592
- return "Unknown";
593
- }
594
-
595
- app.listen(3000, () => {
596
- console.log("Webhook server running on port 3000");
597
- });
598
- ```
599
-
600
- ### Message Events
601
-
602
- Handle incoming messages of all types using the comprehensive Message types:
603
-
604
- ```typescript
605
- import { getMessageContent } from "wuzapi";
606
-
607
- async function handleMessage(event) {
608
- const { Info, Message, IsEphemeral, IsViewOnce } = event;
609
- const from = Info.RemoteJid.replace("@s.whatsapp.net", "");
610
- const isGroup = Info.RemoteJid.includes("@g.us");
611
-
612
- console.log(`New message from ${from}${isGroup ? " (group)" : ""}`);
613
-
614
- if (IsEphemeral) console.log("⏱️ Ephemeral message");
615
- if (IsViewOnce) console.log("👁️ View once message");
616
-
617
- // Use the utility function to get structured message content
618
- const messageContent = getMessageContent(Message);
619
-
620
- if (!messageContent) {
621
- console.log("❓ Unknown message type");
622
- return;
623
- }
624
-
625
- // Handle different message types with full type safety
626
- switch (messageContent.type) {
627
- case "text":
628
- console.log(`💬 Text: ${messageContent.content}`);
629
-
630
- // Auto-reply to specific messages
631
- if (messageContent.content.toLowerCase().includes("hello")) {
632
- await client.chat.sendText({
633
- Phone: from,
634
- Body: "👋 Hello! Thanks for your message.",
635
- });
636
- }
637
- break;
638
-
639
- case "extendedText":
640
- console.log(`📝 Extended text: ${messageContent.content.text}`);
641
- if (messageContent.content.canonicalUrl) {
642
- console.log(`🔗 Link preview: ${messageContent.content.canonicalUrl}`);
643
- console.log(`📰 Title: ${messageContent.content.title}`);
644
- }
645
- break;
646
-
647
- case "image":
648
- const imageMsg = messageContent.content;
649
- console.log(`🖼️ Image: ${imageMsg.caption || "No caption"}`);
650
- console.log(`📏 Dimensions: ${imageMsg.width}x${imageMsg.height}`);
651
-
652
- // Download the image with proper types
653
- if (imageMsg.url && imageMsg.mediaKey) {
654
- try {
655
- const media = await client.chat.downloadImage({
656
- Url: imageMsg.url,
657
- MediaKey: Array.from(imageMsg.mediaKey),
658
- Mimetype: imageMsg.mimetype,
659
- FileSHA256: Array.from(imageMsg.fileSha256),
660
- FileLength: imageMsg.fileLength,
661
- });
662
- console.log("✅ Image downloaded successfully");
663
- } catch (error) {
664
- console.log("❌ Failed to download image:", error.message);
665
- }
666
- }
667
- break;
668
-
669
- case "video":
670
- const videoMsg = messageContent.content;
671
- console.log(`🎥 Video: ${videoMsg.caption || "No caption"}`);
672
- console.log(`⏱️ Duration: ${videoMsg.seconds}s`);
673
- if (videoMsg.gifPlayback) {
674
- console.log("🎬 GIF playback enabled");
675
- }
676
- break;
677
-
678
- case "audio":
679
- const audioMsg = messageContent.content;
680
- console.log(`🎵 Audio: ${audioMsg.seconds}s`);
681
- if (audioMsg.ptt) {
682
- console.log("🎤 Voice note (Push-to-talk)");
683
- }
684
- break;
685
-
686
- case "document":
687
- const docMsg = messageContent.content;
688
- console.log(`📄 Document: ${docMsg.fileName || docMsg.title}`);
689
- console.log(`📊 Size: ${docMsg.fileLength} bytes`);
690
- console.log(`📋 Type: ${docMsg.mimetype}`);
691
- break;
692
-
693
- case "location":
694
- const locMsg = messageContent.content;
695
- console.log(
696
- `📍 Location: ${locMsg.degreesLatitude}, ${locMsg.degreesLongitude}`
697
- );
698
- if (locMsg.name) console.log(`🏷️ Name: ${locMsg.name}`);
699
- if (locMsg.isLiveLocation) console.log("📡 Live location sharing");
700
- break;
701
-
702
- case "contact":
703
- console.log(`👤 Contact: ${messageContent.content.displayName}`);
704
- break;
705
-
706
- case "sticker":
707
- const stickerMsg = messageContent.content;
708
- console.log(`😀 Sticker`);
709
- if (stickerMsg.isAnimated) console.log("🎬 Animated");
710
- if (stickerMsg.isLottie) console.log("🎨 Lottie");
711
- break;
712
-
713
- case "buttons":
714
- const btnMsg = messageContent.content;
715
- console.log(`🔘 Interactive buttons: ${btnMsg.contentText}`);
716
- console.log(`🔢 ${btnMsg.buttons?.length || 0} buttons`);
717
- break;
718
-
719
- case "list":
720
- const listMsg = messageContent.content;
721
- console.log(`📋 List: ${listMsg.title}`);
722
- console.log(`📝 ${listMsg.sections?.length || 0} sections`);
723
- break;
724
-
725
- case "buttonsResponse":
726
- const btnResponse = messageContent.content;
727
- console.log(`✅ Button clicked: ${btnResponse.selectedDisplayText}`);
728
- console.log(`🆔 Button ID: ${btnResponse.selectedButtonId}`);
729
-
730
- // Handle button responses
731
- switch (btnResponse.selectedButtonId) {
732
- case "help":
701
+
702
+ // Handle messages
703
+ if (event.Message && event.Info) {
704
+ const messageContent = getMessageContent(event.Message);
705
+ const from = event.Info.RemoteJid.replace("@s.whatsapp.net", "");
706
+
707
+ if (messageContent?.type === "text") {
708
+ console.log(`Message from ${from}: ${messageContent.content}`);
709
+
710
+ // Auto-reply
711
+ if (messageContent.content.toLowerCase().includes("hello")) {
733
712
  await client.chat.sendText({
734
713
  Phone: from,
735
- Body: "🆘 How can I help you?",
714
+ Body: "Hello! 👋 How can I help you?",
736
715
  });
737
- break;
738
- case "info":
739
- await client.chat.sendText({
740
- Phone: from,
741
- Body: "ℹ️ Here's some information...",
742
- });
743
- break;
744
- }
745
- break;
746
-
747
- case "listResponse":
748
- const listResponse = messageContent.content;
749
- console.log(`✅ List selection: ${listResponse.title}`);
750
- if (listResponse.singleSelectReply) {
751
- console.log(
752
- `🆔 Selected: ${listResponse.singleSelectReply.selectedRowId}`
753
- );
754
- }
755
- break;
756
-
757
- case "poll":
758
- const pollMsg = messageContent.content;
759
- console.log(`📊 Poll: ${pollMsg.name}`);
760
- console.log(`🔢 Options: ${pollMsg.options?.length || 0}`);
761
- break;
762
-
763
- case "reaction":
764
- const reactionMsg = messageContent.content;
765
- console.log(`😊 Reaction: ${reactionMsg.text}`);
766
- console.log(`📝 To message: ${reactionMsg.key?.id}`);
767
- break;
768
-
769
- case "groupInvite":
770
- const inviteMsg = messageContent.content;
771
- console.log(`👥 Group invite: ${inviteMsg.groupName}`);
772
- console.log(`🔗 Code: ${inviteMsg.inviteCode}`);
773
- break;
774
-
775
- default:
776
- console.log(`❓ Unhandled message type: ${messageContent.type}`);
777
- }
778
- }
779
- ```
780
-
781
- ### Read Receipts and Delivery Confirmations
782
-
783
- Handle message delivery and read confirmations:
784
-
785
- ```typescript
786
- async function handleReceipt(event) {
787
- const { MessageSource, MessageIDs, Type, Timestamp } = event;
788
- const from = MessageSource.Chat.User;
789
-
790
- console.log(
791
- `Receipt from ${from}: ${Type} for ${MessageIDs.length} message(s)`
792
- );
793
-
794
- switch (Type) {
795
- case "delivery":
796
- console.log(`✅ Messages delivered to ${from}`);
797
- // Update your database to mark messages as delivered
798
- break;
799
-
800
- case "read":
801
- console.log(`👀 Messages read by ${from}`);
802
- // Update your database to mark messages as read
803
- break;
804
-
805
- case "played":
806
- console.log(`▶️ Voice/video messages played by ${from}`);
807
- // Update your database to mark media as played
808
- break;
809
- }
810
- }
811
- ```
812
-
813
- ### Presence and Typing Indicators
814
-
815
- Handle user online/offline status and typing indicators:
816
-
817
- ```typescript
818
- // User online/offline status
819
- async function handlePresence(event) {
820
- const { From, Unavailable, LastSeen } = event;
821
- const user = From.User;
822
-
823
- if (Unavailable) {
824
- console.log(`🔴 ${user} went offline (last seen: ${LastSeen})`);
825
- } else {
826
- console.log(`🟢 ${user} is online`);
827
- }
828
- }
829
-
830
- // Typing indicators
831
- async function handleChatPresence(event) {
832
- const { MessageSource, State, Media } = event;
833
- const from = MessageSource.Sender.User;
834
- const isGroup = MessageSource.IsGroup;
835
-
836
- if (State === "composing") {
837
- if (Media === "text") {
838
- console.log(`⌨️ ${from} is typing${isGroup ? " in group" : ""}...`);
839
- } else {
840
- console.log(
841
- `📎 ${from} is sending ${Media}${isGroup ? " in group" : ""}...`
842
- );
843
- }
844
- } else if (State === "paused") {
845
- console.log(`⏸️ ${from} stopped typing`);
846
- }
847
- }
848
- ```
849
-
850
- ### Group Events
851
-
852
- Handle group-related events:
853
-
854
- ```typescript
855
- // Group info updates
856
- async function handleGroupInfo(event) {
857
- const { JID, GroupName, Participants, Sender } = event;
858
- console.log(`👥 Group info updated for ${GroupName} (${JID.User})`);
859
- console.log(`👤 Updated by: ${Sender.User}`);
860
- console.log(`👥 Participants: ${Participants.length}`);
861
- }
862
-
863
- // When you're added to a group
864
- async function handleJoinedGroup(event) {
865
- const { Reason, Type, Participants } = event;
866
- console.log(`🎉 Joined group! Reason: ${Reason}, Type: ${Type}`);
867
- console.log(`👥 Group has ${Participants.length} participants`);
868
-
869
- // Send welcome message
870
- // Note: You'll need the group JID from the event context
871
- }
872
- ```
873
-
874
- ### Connection Events
875
-
876
- Handle connection status changes:
877
-
878
- ```typescript
879
- async function handleConnected(event) {
880
- console.log("🟢 Connected to WhatsApp!");
881
- // Your bot is now ready to send messages
882
- }
883
-
884
- async function handleDisconnected(event) {
885
- console.log("🔴 Disconnected from WhatsApp");
886
- // Attempt to reconnect or notify administrators
887
- }
888
-
889
- async function handleLoggedOut(event) {
890
- const { Reason, OnConnect } = event;
891
- console.log(`🚪 Logged out from WhatsApp. Reason: ${Reason}`);
892
-
893
- if (OnConnect) {
894
- console.log("⚠️ Logout occurred during connection");
895
- }
896
-
897
- // You may need to scan QR code again
898
- }
899
- ```
900
-
901
- ### QR Code Events
902
-
903
- Handle QR code generation for initial setup:
904
-
905
- ```typescript
906
- async function handleQR(event) {
907
- const { Codes } = event;
908
- console.log("📱 New QR codes received:");
909
-
910
- Codes.forEach((code, index) => {
911
- console.log(`📷 QR Code ${index + 1}: ${code}`);
912
- // Display QR code to user or save to file
913
- });
914
- }
915
- ```
916
-
917
- ### Profile and Contact Updates
918
-
919
- Handle profile picture and contact information changes:
920
-
921
- ```typescript
922
- // Profile picture changes
923
- async function handlePicture(event) {
924
- const { JID, Author, Remove, Timestamp } = event;
925
- const target = JID.User;
926
- const changer = Author.User;
927
-
928
- if (Remove) {
929
- console.log(`🗑️ ${changer} removed profile picture for ${target}`);
930
- } else {
931
- console.log(`🖼️ ${changer} updated profile picture for ${target}`);
932
- }
933
- }
934
-
935
- // Contact info updates
936
- async function handleContact(event) {
937
- const { JID, Found, FullName, PushName, BusinessName } = event;
938
- console.log(`👤 Contact info: ${FullName || PushName} (${JID.User})`);
939
-
940
- if (BusinessName) {
941
- console.log(`🏢 Business: ${BusinessName}`);
942
- }
943
-
944
- console.log(`✅ Found in WhatsApp: ${Found}`);
945
- }
946
-
947
- // Name changes
948
- async function handlePushName(event) {
949
- const { JID, OldPushName, NewPushName } = event;
950
- console.log(
951
- `📝 ${JID.User} changed name from "${OldPushName}" to "${NewPushName}"`
952
- );
953
- }
954
- ```
955
-
956
- ### Error Handling
957
-
958
- Handle various error events:
959
-
960
- ```typescript
961
- // Undecryptable messages
962
- async function handleUndecryptableMessage(event) {
963
- const { Info, IsUnavailable, UnavailableType, DecryptFailMode } = event;
964
- console.log(`❌ Failed to decrypt message from ${Info.Source.Sender.User}`);
965
- console.log(
966
- `📊 Unavailable: ${IsUnavailable}, Type: ${UnavailableType}, Mode: ${DecryptFailMode}`
967
- );
968
-
969
- // Log for debugging or request message retry
970
- }
971
-
972
- // Stream errors
973
- async function handleStreamError(event) {
974
- const { Code } = event;
975
- console.log(`🚨 Stream error: ${Code}`);
976
-
977
- // Handle specific error codes
978
- switch (Code) {
979
- case "conflict":
980
- console.log("Another client connected with same credentials");
981
- break;
982
- case "stream:error":
983
- console.log("General stream error occurred");
984
- break;
985
- default:
986
- console.log("Unknown stream error");
987
- }
988
- }
989
- ```
990
-
991
- ### Complete Webhook Server Example
992
-
993
- Here's a complete webhook server that handles all event types:
994
-
995
- ```typescript
996
- import express from "express";
997
- import WuzapiClient, {
998
- Message,
999
- Receipt,
1000
- Presence,
1001
- EventGroupInfo,
1002
- } from "wuzapi";
1003
-
1004
- const app = express();
1005
- app.use(express.json());
1006
-
1007
- const client = new WuzapiClient({
1008
- apiUrl: process.env.WUZAPI_URL || "http://localhost:8080",
1009
- token: process.env.WUZAPI_TOKEN || "your-token",
1010
- });
1011
-
1012
- // Event router
1013
- const eventHandlers = {
1014
- Message: handleMessage,
1015
- Receipt: handleReceipt,
1016
- Presence: handlePresence,
1017
- ChatPresence: handleChatPresence,
1018
- GroupInfo: handleGroupInfo,
1019
- JoinedGroup: handleJoinedGroup,
1020
- Connected: handleConnected,
1021
- Disconnected: handleDisconnected,
1022
- LoggedOut: handleLoggedOut,
1023
- QR: handleQR,
1024
- Picture: handlePicture,
1025
- Contact: handleContact,
1026
- PushName: handlePushName,
1027
- UndecryptableMessage: handleUndecryptableMessage,
1028
- StreamError: handleStreamError,
1029
- };
1030
-
1031
- app.post("/webhook", async (req, res) => {
1032
- try {
1033
- const eventData = req.body;
1034
-
1035
- // Log all events for debugging
1036
- console.log("📨 Webhook received:", JSON.stringify(eventData, null, 2));
1037
-
1038
- // Route to appropriate handler based on event structure
1039
- for (const [eventType, handler] of Object.entries(eventHandlers)) {
1040
- if (isEventType(eventData, eventType)) {
1041
- await handler(eventData);
1042
- break;
1043
- }
1044
- }
1045
-
1046
- res.status(200).json({ success: true });
1047
- } catch (error) {
1048
- console.error("❌ Webhook processing error:", error);
1049
- res.status(500).json({ error: error.message });
1050
- }
1051
- });
1052
-
1053
- // Helper function to identify event types
1054
- function isEventType(event, type) {
1055
- switch (type) {
1056
- case "Message":
1057
- return event.Message && event.Info;
1058
- case "Receipt":
1059
- return event.MessageIDs && event.Type;
1060
- case "Presence":
1061
- return event.From && typeof event.Unavailable !== "undefined";
1062
- case "ChatPresence":
1063
- return event.State && event.MessageSource;
1064
- case "GroupInfo":
1065
- return event.GroupName && event.Participants;
1066
- case "QR":
1067
- return event.Codes;
1068
- // Add more type checks as needed
1069
- default:
1070
- return false;
1071
- }
1072
- }
1073
-
1074
- // Health check
1075
- app.get("/health", (req, res) => {
1076
- res.json({ status: "healthy", timestamp: new Date().toISOString() });
1077
- });
1078
-
1079
- // Initialize
1080
- async function initialize() {
1081
- try {
1082
- // Connect to WhatsApp with all events using EventType enum
1083
- await client.session.connect({
1084
- Subscribe: [
1085
- EventType.MESSAGE,
1086
- EventType.RECEIPT,
1087
- EventType.PRESENCE,
1088
- EventType.CHAT_PRESENCE,
1089
- EventType.GROUP_INFO,
1090
- EventType.CONTACT,
1091
- EventType.PUSH_NAME,
1092
- EventType.PICTURE,
1093
- EventType.QR,
1094
- EventType.CONNECTED,
1095
- EventType.DISCONNECTED,
1096
- EventType.LOGGED_OUT,
1097
- EventType.UNDECRYPTABLE_MESSAGE,
1098
- EventType.STREAM_ERROR,
1099
- ],
1100
- Immediate: false,
1101
- });
1102
-
1103
- // Set webhook
1104
- const webhookUrl =
1105
- process.env.WEBHOOK_URL || "http://localhost:3000/webhook";
1106
- await client.webhook.setWebhook(webhookUrl);
1107
-
1108
- app.listen(3000, () => {
1109
- console.log("🚀 Webhook server running on port 3000");
1110
- console.log("🎉 Ready to receive WhatsApp events!");
1111
- });
1112
- } catch (error) {
1113
- console.error("❌ Initialization failed:", error);
1114
- process.exit(1);
1115
- }
1116
- }
1117
-
1118
- initialize();
1119
- ```
1120
-
1121
- ### Event Types Reference
1122
-
1123
- All webhook events are fully typed. Import the specific event types you need:
1124
-
1125
- ```typescript
1126
- import {
1127
- // Event type enum for all possible events
1128
- EventType,
1129
-
1130
- // Core message types
1131
- Message,
1132
- MessageEvent,
1133
- MessageContent,
1134
- getMessageContent,
1135
-
1136
- // Specific message types
1137
- ImageMessage,
1138
- VideoMessage,
1139
- AudioMessage,
1140
- DocumentMessage,
1141
- LocationMessage,
1142
- ContactMessage,
1143
- StickerMessage,
1144
- ButtonsMessage,
1145
- ListMessage,
1146
- PollCreationMessage,
1147
- ReactionMessage,
1148
-
1149
- // Webhook payload types
1150
- WebhookPayload,
1151
- S3MediaInfo,
1152
- S3OnlyWebhookPayload,
1153
- Base64OnlyWebhookPayload,
1154
- BothMediaWebhookPayload,
1155
- hasS3Media,
1156
- hasBase64Media,
1157
- hasBothMedia,
1158
-
1159
- // Event types
1160
- Receipt,
1161
- Presence,
1162
- ChatPresence,
1163
- EventGroupInfo,
1164
- EventContact,
1165
- QR,
1166
- Picture,
1167
- PushName,
1168
- Connected,
1169
- Disconnected,
1170
- LoggedOut,
1171
- UndecryptableMessage,
1172
- StreamError,
1173
- WhatsAppEvent,
1174
- } from "wuzapi";
1175
-
1176
- // Type-safe event handling with EventType enum
1177
- async function handleTypedWebhook(webhookPayload: WebhookPayload) {
1178
- const event = webhookPayload.event;
1179
-
1180
- // Type-safe event detection using EventType enum
1181
- const eventType = detectEventType(event);
1182
-
1183
- switch (eventType) {
1184
- case EventType.MESSAGE:
1185
- const messageEvent = event as MessageEvent;
1186
- const messageContent = getMessageContent(messageEvent.Message);
1187
-
1188
- if (messageContent?.type === "image") {
1189
- // TypeScript knows this is an ImageMessage
1190
- const imageMsg: ImageMessage = messageContent.content;
1191
- console.log(`Image dimensions: ${imageMsg.width}x${imageMsg.height}`);
1192
-
1193
- // Handle S3 media if available
1194
- if (hasS3Media(webhookPayload)) {
1195
- console.log(`S3 URL: ${webhookPayload.s3.url}`);
1196
- // Download from S3 or process as needed
1197
- }
1198
-
1199
- // Handle Base64 media if available
1200
- if (hasBase64Media(webhookPayload)) {
1201
- console.log("Processing base64 image data");
1202
- // Process base64 data: webhookPayload.base64
1203
716
  }
1204
717
  }
1205
- break;
1206
-
1207
- case EventType.RECEIPT:
1208
- const receiptEvent = event as Receipt;
1209
- console.log(`Receipt type: ${receiptEvent.Type}`);
1210
- break;
1211
-
1212
- case EventType.PRESENCE:
1213
- const presenceEvent = event as Presence;
1214
- console.log(`User ${presenceEvent.From.User} presence update`);
1215
- break;
1216
-
1217
- default:
1218
- console.log(`Unhandled event: ${eventType}`);
1219
- }
1220
- }
1221
-
1222
- // Complete webhook server with S3 support
1223
- app.post("/webhook", async (req, res) => {
1224
- try {
1225
- const payload: WebhookPayload = req.body;
1226
- await handleTypedWebhook(payload);
1227
- res.status(200).json({ success: true });
718
+ }
719
+
720
+ res.json({ success: true });
1228
721
  } catch (error) {
1229
722
  console.error("Webhook error:", error);
1230
723
  res.status(500).json({ error: error.message });
1231
724
  }
1232
725
  });
1233
-
1234
- // Helper function to handle all message types generically
1235
- function processMessageContent(content: MessageContent) {
1236
- switch (content.type) {
1237
- case "text":
1238
- // content.content is string
1239
- return content.content.toUpperCase();
1240
-
1241
- case "image":
1242
- // content.content is ImageMessage
1243
- return `Image: ${content.content.caption || "No caption"}`;
1244
-
1245
- case "video":
1246
- // content.content is VideoMessage
1247
- return `Video (${content.content.seconds}s): ${
1248
- content.content.caption || "No caption"
1249
- }`;
1250
-
1251
- // TypeScript ensures you handle all possible types
1252
- default:
1253
- return `Unknown message type: ${content.type}`;
1254
- }
1255
- }
1256
726
  ```
1257
727
 
1258
- ### Advanced Message Type Handling
728
+ ### Message Types
1259
729
 
1260
- For complex message processing, you can work directly with the typed message structures:
730
+ The `getMessageContent()` utility function returns structured message data:
1261
731
 
1262
732
  ```typescript
1263
- import {
1264
- Message,
1265
- ExtendedTextMessage,
1266
- ButtonsMessage,
1267
- ContextInfo,
1268
- } from "wuzapi";
733
+ const messageContent = getMessageContent(event.Message);
1269
734
 
1270
- // Check for extended text with link preview
1271
- function hasLinkPreview(message: Message): boolean {
1272
- return !!message.extendedTextMessage?.canonicalUrl;
1273
- }
1274
-
1275
- // Extract context info from any message
1276
- function getMessageContext(message: Message): ContextInfo | undefined {
1277
- // Check various message types for context info
1278
- return (
1279
- message.extendedTextMessage?.contextInfo ||
1280
- message.imageMessage?.contextInfo ||
1281
- message.videoMessage?.contextInfo ||
1282
- message.audioMessage?.contextInfo
1283
- );
1284
- }
1285
-
1286
- // Process interactive messages
1287
- function handleInteractiveMessage(message: Message) {
1288
- if (message.buttonsMessage) {
1289
- const btns = message.buttonsMessage;
1290
- console.log(`Interactive message: ${btns.contentText}`);
1291
-
1292
- btns.buttons?.forEach((button) => {
1293
- if (button.type === ButtonType.RESPONSE) {
1294
- console.log(`Response button: ${button.buttonText?.displayText}`);
1295
- } else if (button.type === ButtonType.NATIVE_FLOW) {
1296
- console.log(`Native flow: ${button.nativeFlowInfo?.name}`);
1297
- }
1298
- });
1299
- }
1300
-
1301
- if (message.listMessage) {
1302
- const list = message.listMessage;
1303
- console.log(`List: ${list.title}`);
1304
-
1305
- list.sections?.forEach((section) => {
1306
- console.log(`Section: ${section.title}`);
1307
- section.rows?.forEach((row) => {
1308
- console.log(`- ${row.title}: ${row.description}`);
1309
- });
1310
- });
1311
- }
735
+ switch (messageContent?.type) {
736
+ case "text":
737
+ console.log("Text:", messageContent.content);
738
+ break;
739
+ case "image":
740
+ console.log("Image:", messageContent.content.caption);
741
+ break;
742
+ case "buttonsResponse":
743
+ console.log("Button clicked:", messageContent.content.selectedButtonId);
744
+ break;
745
+ case "listResponse":
746
+ console.log("List selection:", messageContent.content.singleSelectReply?.selectedRowId);
747
+ break;
748
+ // ... handle other types
1312
749
  }
1313
750
  ```
1314
751
 
1315
- ## Error Handling
752
+ ---
753
+
754
+ ## 🛠️ Advanced Topics
1316
755
 
1317
- The library provides comprehensive error handling with detailed error information:
756
+ <details>
757
+ <summary><strong>⚠️ Error Handling</strong></summary>
1318
758
 
1319
759
  ```typescript
1320
760
  import { WuzapiError } from "wuzapi";
@@ -1337,52 +777,37 @@ try {
1337
777
  }
1338
778
  ```
1339
779
 
1340
- ### Token Authentication Errors
780
+ ### Common Error Codes
781
+ - **401**: Authentication required
782
+ - **404**: Endpoint not found
783
+ - **500**: Server error
1341
784
 
1342
- When no token is provided (either globally or per-request), the library will throw a specific error:
785
+ </details>
1343
786
 
1344
- ```typescript
1345
- import { WuzapiError } from "wuzapi";
787
+ <details>
788
+ <summary><strong>🔧 Custom Configuration</strong></summary>
1346
789
 
1347
- // Client without global token
1348
- const client = new WuzapiClient({
1349
- apiUrl: "http://localhost:8080",
1350
- // No token provided
1351
- });
790
+ ```typescript
791
+ // Custom axios configuration
792
+ import { BaseClient } from "wuzapi";
1352
793
 
1353
- try {
1354
- // This will fail - no token provided
1355
- await client.chat.sendText({
1356
- Phone: "5491155554444",
1357
- Body: "This will fail",
1358
- });
1359
- } catch (error) {
1360
- if (error instanceof WuzapiError && error.code === 401) {
1361
- console.error("Authentication required:", error.message);
1362
- // "No authentication token provided. Either set a token in the client config or provide one in the request options."
794
+ class CustomClient extends BaseClient {
795
+ constructor(config) {
796
+ super(config);
797
+
798
+ // Add custom interceptors
799
+ this.axios.interceptors.request.use((config) => {
800
+ console.log("Making request:", config.url);
801
+ return config;
802
+ });
1363
803
  }
1364
804
  }
1365
-
1366
- // Fix by providing token
1367
- await client.chat.sendText(
1368
- {
1369
- Phone: "5491155554444",
1370
- Body: "Now it works!",
1371
- },
1372
- { token: "your-token" }
1373
- );
1374
805
  ```
1375
806
 
1376
- ### Error Types
1377
-
1378
- - **Network Errors**: Connection issues, timeouts
1379
- - **Authentication Errors**: Invalid tokens, permission denied
1380
- - **API Errors**: Invalid parameters, service unavailable
1381
- - **Validation Errors**: Missing required fields, invalid data formats
1382
-
1383
- ## TypeScript Support
807
+ </details>
1384
808
 
1385
- The library is built with TypeScript and provides complete type definitions:
809
+ <details>
810
+ <summary><strong>📝 TypeScript Support</strong></summary>
1386
811
 
1387
812
  ```typescript
1388
813
  import {
@@ -1402,9 +827,10 @@ const request: SendTextRequest = {
1402
827
  const response: SendMessageResponse = await client.chat.sendText(request);
1403
828
  ```
1404
829
 
1405
- ## Legacy Aliases
830
+ </details>
1406
831
 
1407
- For convenience, the library provides some legacy aliases:
832
+ <details>
833
+ <summary><strong>🔄 Legacy Aliases</strong></summary>
1408
834
 
1409
835
  ```typescript
1410
836
  // These are equivalent:
@@ -1415,130 +841,11 @@ await client.chat.sendText({ Phone: "123", Body: "Hi" });
1415
841
  await client.message.sendText({ Phone: "123", Body: "Hi" }); // Alias
1416
842
  ```
1417
843
 
1418
- ## Advanced Usage
1419
-
1420
- ### Custom Axios Configuration
1421
-
1422
- You can extend the base client for custom axios configuration:
1423
-
1424
- ```typescript
1425
- import { BaseClient } from "wuzapi";
1426
-
1427
- class CustomClient extends BaseClient {
1428
- constructor(config: WuzapiConfig) {
1429
- super(config);
1430
-
1431
- // Add custom interceptors
1432
- this.axios.interceptors.request.use((config) => {
1433
- console.log("Making request:", config.url);
1434
- return config;
1435
- });
1436
- }
1437
- }
1438
- ```
1439
-
1440
- ### Ping Test
1441
-
1442
- Test connectivity to the API:
1443
-
1444
- ```typescript
1445
- const isConnected = await client.ping();
1446
- if (isConnected) {
1447
- console.log("API is reachable");
1448
- } else {
1449
- console.log("API is not reachable");
1450
- }
1451
- ```
1452
-
1453
- ## Examples
1454
-
1455
- For complete working examples, check the `examples/` directory:
1456
-
1457
- - **`basic-usage.js`** - Basic client setup and usage
1458
- - **`chatbot-example.js`** - Simple chatbot with command handling
1459
- - **`webhook-events-example.js`** - Comprehensive webhook event handling
1460
-
1461
- ### Complete Chat Bot Example
1462
-
1463
- ```typescript
1464
- import WuzapiClient from "wuzapi";
1465
-
1466
- const client = new WuzapiClient({
1467
- apiUrl: "http://localhost:8080",
1468
- token: "your-token",
1469
- });
1470
-
1471
- async function startBot() {
1472
- // Connect to WhatsApp
1473
- await client.session.connect({
1474
- Subscribe: ["Message"],
1475
- Immediate: false,
1476
- });
1477
-
1478
- // Wait for connection
1479
- let connected = false;
1480
- while (!connected) {
1481
- const status = await client.session.getStatus();
1482
- if (!status.LoggedIn) {
1483
- const qr = await client.session.getQRCode();
1484
- console.log("Scan this QR code:", qr.QRCode);
1485
- await new Promise((resolve) => setTimeout(resolve, 5000));
1486
- } else {
1487
- connected = true;
1488
- console.log("Bot connected and ready!");
1489
- }
1490
- }
1491
-
1492
- // Set webhook for receiving messages
1493
- await client.webhook.setWebhook("https://your-server.com/webhook");
1494
- }
1495
-
1496
- // Handle incoming messages in your webhook endpoint
1497
- function handleIncomingMessage(message: any) {
1498
- const phone = message.Info.RemoteJid.replace("@s.whatsapp.net", "");
1499
- const text = message.Message?.conversation || "";
1500
-
1501
- if (text.toLowerCase() === "hello") {
1502
- client.chat.sendText({
1503
- Phone: phone,
1504
- Body: "Hello! How can I help you today?",
1505
- });
1506
- }
1507
- }
1508
-
1509
- startBot().catch(console.error);
1510
- ```
1511
-
1512
- ### Group Management Example
1513
-
1514
- ```typescript
1515
- async function manageGroup() {
1516
- // Create a new group
1517
- const group = await client.group.create("Project Team", [
1518
- "5491155553934",
1519
- "5491155553935",
1520
- ]);
1521
-
1522
- console.log("Created group:", group.JID);
1523
-
1524
- // Set group photo
1525
- await client.group.setPhoto(group.JID, "data:image/jpeg;base64,...");
1526
-
1527
- // Configure group settings
1528
- await client.group.setLocked(group.JID, true); // Only admins can modify
1529
- await client.group.setEphemeral(group.JID, "7d"); // Messages disappear after 7 days
1530
-
1531
- // Get and share invite link
1532
- const invite = await client.group.getInviteLink(group.JID);
1533
- console.log("Invite link:", invite.InviteLink);
1534
- }
1535
- ```
1536
-
1537
- ## API Reference
844
+ </details>
1538
845
 
1539
- For detailed API documentation, refer to the [WuzAPI documentation](https://github.com/asternic/wuzapi/blob/main/API.md).
846
+ ---
1540
847
 
1541
- ## Contributing
848
+ ## 🤝 Contributing
1542
849
 
1543
850
  1. Fork the repository
1544
851
  2. Create your feature branch (`git checkout -b feature/amazing-feature`)
@@ -1561,43 +868,32 @@ npm run lint
1561
868
 
1562
869
  # Build the project
1563
870
  npm run build
1564
-
1565
- # Run development server
1566
- npm run dev
1567
871
  ```
1568
872
 
1569
- ### Code Style
1570
-
1571
- This project uses ESLint and TypeScript. Please ensure your code passes all checks:
1572
-
1573
- ```bash
1574
- npm run lint
1575
- npm run lint:fix # Auto-fix issues
1576
- ```
1577
-
1578
- ## License
873
+ ## 📄 License
1579
874
 
1580
875
  MIT License - see the [LICENSE](LICENSE) file for details.
1581
876
 
1582
- ## Support
877
+ ## 🔗 Links
1583
878
 
1584
- - 📚 [Documentation](https://github.com/asternic/wuzapi)
879
+ - 📚 [WuzAPI Documentation](https://github.com/asternic/wuzapi)
1585
880
  - 🐛 [Issue Tracker](https://github.com/gusnips/wuzapi-node/issues)
1586
881
  - 💬 [Discussions](https://github.com/gusnips/wuzapi-node/discussions)
1587
882
 
1588
- ## Changelog
883
+ ---
884
+
885
+ ## 📊 Changelog
1589
886
 
1590
- ### 1.0.0
887
+ ### Latest Updates
1591
888
 
1592
- - Initial release
1593
- - Full TypeScript support
1594
- - Complete API coverage
1595
- - Modular architecture
1596
- - Comprehensive error handling
1597
- - S3 storage integration support
1598
- - Admin user management
1599
- - Group management features
1600
- - Webhook configuration
889
+ - **Phone Pairing**: Alternative to QR code login
890
+ - **Interactive Messages**: Buttons, lists, and polls
891
+ - **Message Management**: Edit and delete messages
892
+ - **Advanced Groups**: Full participant management
893
+ - **Newsletter Support**: Business newsletter features
894
+ - **Enhanced Webhooks**: Update and delete webhook configs
895
+ - **Proxy Support**: Configure proxy for connections
896
+ - **History Sync**: Request message history after login
1601
897
 
1602
898
  ---
1603
899