wuzapi 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -285,7 +285,7 @@ await adminClient.admin.deleteUser(2);
285
285
 
286
286
  ### Webhook Module
287
287
 
288
- Configure webhook settings.
288
+ Configure webhook settings and handle incoming WhatsApp events.
289
289
 
290
290
  ```typescript
291
291
  // Set webhook URL
@@ -297,6 +297,721 @@ console.log("Webhook URL:", webhookConfig.webhook);
297
297
  console.log("Subscribed events:", webhookConfig.subscribe);
298
298
  ```
299
299
 
300
+ ## Webhook Event Handling
301
+
302
+ WuzAPI sends real-time events to your webhook endpoint. Here's how to handle different types of events:
303
+
304
+ ### Basic Webhook Setup
305
+
306
+ ```typescript
307
+ import express from "express";
308
+ import WuzapiClient from "wuzapi";
309
+
310
+ const app = express();
311
+ app.use(express.json());
312
+
313
+ const client = new WuzapiClient({
314
+ apiUrl: "http://localhost:8080",
315
+ token: "your-token",
316
+ });
317
+
318
+ // Webhook endpoint
319
+ app.post("/webhook", async (req, res) => {
320
+ try {
321
+ const event = req.body;
322
+
323
+ // Handle different event types
324
+ if (event.Message) {
325
+ await handleMessage(event);
326
+ } else if (event.MessageIDs && event.Type) {
327
+ await handleReceipt(event);
328
+ } else if (event.From && typeof event.Unavailable !== "undefined") {
329
+ await handlePresence(event);
330
+ }
331
+
332
+ res.status(200).json({ success: true });
333
+ } catch (error) {
334
+ console.error("Webhook error:", error);
335
+ res.status(500).json({ error: error.message });
336
+ }
337
+ });
338
+
339
+ app.listen(3000, () => {
340
+ console.log("Webhook server running on port 3000");
341
+ });
342
+ ```
343
+
344
+ ### Message Events
345
+
346
+ Handle incoming messages of all types using the comprehensive Message types:
347
+
348
+ ```typescript
349
+ import { getMessageContent } from "wuzapi";
350
+
351
+ async function handleMessage(event) {
352
+ const { Info, Message, IsEphemeral, IsViewOnce } = event;
353
+ const from = Info.RemoteJid.replace("@s.whatsapp.net", "");
354
+ const isGroup = Info.RemoteJid.includes("@g.us");
355
+
356
+ console.log(`New message from ${from}${isGroup ? " (group)" : ""}`);
357
+
358
+ if (IsEphemeral) console.log("⏱️ Ephemeral message");
359
+ if (IsViewOnce) console.log("👁️ View once message");
360
+
361
+ // Use the utility function to get structured message content
362
+ const messageContent = getMessageContent(Message);
363
+
364
+ if (!messageContent) {
365
+ console.log("❓ Unknown message type");
366
+ return;
367
+ }
368
+
369
+ // Handle different message types with full type safety
370
+ switch (messageContent.type) {
371
+ case "text":
372
+ console.log(`💬 Text: ${messageContent.content}`);
373
+
374
+ // Auto-reply to specific messages
375
+ if (messageContent.content.toLowerCase().includes("hello")) {
376
+ await client.chat.sendText({
377
+ Phone: from,
378
+ Body: "👋 Hello! Thanks for your message.",
379
+ });
380
+ }
381
+ break;
382
+
383
+ case "extendedText":
384
+ console.log(`📝 Extended text: ${messageContent.content.text}`);
385
+ if (messageContent.content.canonicalUrl) {
386
+ console.log(`🔗 Link preview: ${messageContent.content.canonicalUrl}`);
387
+ console.log(`📰 Title: ${messageContent.content.title}`);
388
+ }
389
+ break;
390
+
391
+ case "image":
392
+ const imageMsg = messageContent.content;
393
+ console.log(`🖼️ Image: ${imageMsg.caption || "No caption"}`);
394
+ console.log(`📏 Dimensions: ${imageMsg.width}x${imageMsg.height}`);
395
+
396
+ // Download the image with proper types
397
+ if (imageMsg.url && imageMsg.mediaKey) {
398
+ try {
399
+ const media = await client.chat.downloadImage({
400
+ Url: imageMsg.url,
401
+ MediaKey: Array.from(imageMsg.mediaKey),
402
+ Mimetype: imageMsg.mimetype,
403
+ FileSHA256: Array.from(imageMsg.fileSha256),
404
+ FileLength: imageMsg.fileLength,
405
+ });
406
+ console.log("✅ Image downloaded successfully");
407
+ } catch (error) {
408
+ console.log("❌ Failed to download image:", error.message);
409
+ }
410
+ }
411
+ break;
412
+
413
+ case "video":
414
+ const videoMsg = messageContent.content;
415
+ console.log(`🎥 Video: ${videoMsg.caption || "No caption"}`);
416
+ console.log(`⏱️ Duration: ${videoMsg.seconds}s`);
417
+ if (videoMsg.gifPlayback) {
418
+ console.log("🎬 GIF playback enabled");
419
+ }
420
+ break;
421
+
422
+ case "audio":
423
+ const audioMsg = messageContent.content;
424
+ console.log(`🎵 Audio: ${audioMsg.seconds}s`);
425
+ if (audioMsg.ptt) {
426
+ console.log("🎤 Voice note (Push-to-talk)");
427
+ }
428
+ break;
429
+
430
+ case "document":
431
+ const docMsg = messageContent.content;
432
+ console.log(`📄 Document: ${docMsg.fileName || docMsg.title}`);
433
+ console.log(`📊 Size: ${docMsg.fileLength} bytes`);
434
+ console.log(`📋 Type: ${docMsg.mimetype}`);
435
+ break;
436
+
437
+ case "location":
438
+ const locMsg = messageContent.content;
439
+ console.log(
440
+ `📍 Location: ${locMsg.degreesLatitude}, ${locMsg.degreesLongitude}`
441
+ );
442
+ if (locMsg.name) console.log(`🏷️ Name: ${locMsg.name}`);
443
+ if (locMsg.isLiveLocation) console.log("📡 Live location sharing");
444
+ break;
445
+
446
+ case "contact":
447
+ console.log(`👤 Contact: ${messageContent.content.displayName}`);
448
+ break;
449
+
450
+ case "sticker":
451
+ const stickerMsg = messageContent.content;
452
+ console.log(`😀 Sticker`);
453
+ if (stickerMsg.isAnimated) console.log("🎬 Animated");
454
+ if (stickerMsg.isLottie) console.log("🎨 Lottie");
455
+ break;
456
+
457
+ case "buttons":
458
+ const btnMsg = messageContent.content;
459
+ console.log(`🔘 Interactive buttons: ${btnMsg.contentText}`);
460
+ console.log(`🔢 ${btnMsg.buttons?.length || 0} buttons`);
461
+ break;
462
+
463
+ case "list":
464
+ const listMsg = messageContent.content;
465
+ console.log(`📋 List: ${listMsg.title}`);
466
+ console.log(`📝 ${listMsg.sections?.length || 0} sections`);
467
+ break;
468
+
469
+ case "buttonsResponse":
470
+ const btnResponse = messageContent.content;
471
+ console.log(`✅ Button clicked: ${btnResponse.selectedDisplayText}`);
472
+ console.log(`🆔 Button ID: ${btnResponse.selectedButtonId}`);
473
+
474
+ // Handle button responses
475
+ switch (btnResponse.selectedButtonId) {
476
+ case "help":
477
+ await client.chat.sendText({
478
+ Phone: from,
479
+ Body: "🆘 How can I help you?",
480
+ });
481
+ break;
482
+ case "info":
483
+ await client.chat.sendText({
484
+ Phone: from,
485
+ Body: "ℹ️ Here's some information...",
486
+ });
487
+ break;
488
+ }
489
+ break;
490
+
491
+ case "listResponse":
492
+ const listResponse = messageContent.content;
493
+ console.log(`✅ List selection: ${listResponse.title}`);
494
+ if (listResponse.singleSelectReply) {
495
+ console.log(
496
+ `🆔 Selected: ${listResponse.singleSelectReply.selectedRowId}`
497
+ );
498
+ }
499
+ break;
500
+
501
+ case "poll":
502
+ const pollMsg = messageContent.content;
503
+ console.log(`📊 Poll: ${pollMsg.name}`);
504
+ console.log(`🔢 Options: ${pollMsg.options?.length || 0}`);
505
+ break;
506
+
507
+ case "reaction":
508
+ const reactionMsg = messageContent.content;
509
+ console.log(`😊 Reaction: ${reactionMsg.text}`);
510
+ console.log(`📝 To message: ${reactionMsg.key?.id}`);
511
+ break;
512
+
513
+ case "groupInvite":
514
+ const inviteMsg = messageContent.content;
515
+ console.log(`👥 Group invite: ${inviteMsg.groupName}`);
516
+ console.log(`🔗 Code: ${inviteMsg.inviteCode}`);
517
+ break;
518
+
519
+ default:
520
+ console.log(`❓ Unhandled message type: ${messageContent.type}`);
521
+ }
522
+ }
523
+ ```
524
+
525
+ ### Read Receipts and Delivery Confirmations
526
+
527
+ Handle message delivery and read confirmations:
528
+
529
+ ```typescript
530
+ async function handleReceipt(event) {
531
+ const { MessageSource, MessageIDs, Type, Timestamp } = event;
532
+ const from = MessageSource.Chat.User;
533
+
534
+ console.log(
535
+ `Receipt from ${from}: ${Type} for ${MessageIDs.length} message(s)`
536
+ );
537
+
538
+ switch (Type) {
539
+ case "delivery":
540
+ console.log(`✅ Messages delivered to ${from}`);
541
+ // Update your database to mark messages as delivered
542
+ break;
543
+
544
+ case "read":
545
+ console.log(`👀 Messages read by ${from}`);
546
+ // Update your database to mark messages as read
547
+ break;
548
+
549
+ case "played":
550
+ console.log(`▶️ Voice/video messages played by ${from}`);
551
+ // Update your database to mark media as played
552
+ break;
553
+ }
554
+ }
555
+ ```
556
+
557
+ ### Presence and Typing Indicators
558
+
559
+ Handle user online/offline status and typing indicators:
560
+
561
+ ```typescript
562
+ // User online/offline status
563
+ async function handlePresence(event) {
564
+ const { From, Unavailable, LastSeen } = event;
565
+ const user = From.User;
566
+
567
+ if (Unavailable) {
568
+ console.log(`🔴 ${user} went offline (last seen: ${LastSeen})`);
569
+ } else {
570
+ console.log(`🟢 ${user} is online`);
571
+ }
572
+ }
573
+
574
+ // Typing indicators
575
+ async function handleChatPresence(event) {
576
+ const { MessageSource, State, Media } = event;
577
+ const from = MessageSource.Sender.User;
578
+ const isGroup = MessageSource.IsGroup;
579
+
580
+ if (State === "composing") {
581
+ if (Media === "text") {
582
+ console.log(`⌨️ ${from} is typing${isGroup ? " in group" : ""}...`);
583
+ } else {
584
+ console.log(
585
+ `📎 ${from} is sending ${Media}${isGroup ? " in group" : ""}...`
586
+ );
587
+ }
588
+ } else if (State === "paused") {
589
+ console.log(`⏸️ ${from} stopped typing`);
590
+ }
591
+ }
592
+ ```
593
+
594
+ ### Group Events
595
+
596
+ Handle group-related events:
597
+
598
+ ```typescript
599
+ // Group info updates
600
+ async function handleGroupInfo(event) {
601
+ const { JID, GroupName, Participants, Sender } = event;
602
+ console.log(`👥 Group info updated for ${GroupName} (${JID.User})`);
603
+ console.log(`👤 Updated by: ${Sender.User}`);
604
+ console.log(`👥 Participants: ${Participants.length}`);
605
+ }
606
+
607
+ // When you're added to a group
608
+ async function handleJoinedGroup(event) {
609
+ const { Reason, Type, Participants } = event;
610
+ console.log(`🎉 Joined group! Reason: ${Reason}, Type: ${Type}`);
611
+ console.log(`👥 Group has ${Participants.length} participants`);
612
+
613
+ // Send welcome message
614
+ // Note: You'll need the group JID from the event context
615
+ }
616
+ ```
617
+
618
+ ### Connection Events
619
+
620
+ Handle connection status changes:
621
+
622
+ ```typescript
623
+ async function handleConnected(event) {
624
+ console.log("🟢 Connected to WhatsApp!");
625
+ // Your bot is now ready to send messages
626
+ }
627
+
628
+ async function handleDisconnected(event) {
629
+ console.log("🔴 Disconnected from WhatsApp");
630
+ // Attempt to reconnect or notify administrators
631
+ }
632
+
633
+ async function handleLoggedOut(event) {
634
+ const { Reason, OnConnect } = event;
635
+ console.log(`🚪 Logged out from WhatsApp. Reason: ${Reason}`);
636
+
637
+ if (OnConnect) {
638
+ console.log("⚠️ Logout occurred during connection");
639
+ }
640
+
641
+ // You may need to scan QR code again
642
+ }
643
+ ```
644
+
645
+ ### QR Code Events
646
+
647
+ Handle QR code generation for initial setup:
648
+
649
+ ```typescript
650
+ async function handleQR(event) {
651
+ const { Codes } = event;
652
+ console.log("📱 New QR codes received:");
653
+
654
+ Codes.forEach((code, index) => {
655
+ console.log(`📷 QR Code ${index + 1}: ${code}`);
656
+ // Display QR code to user or save to file
657
+ });
658
+ }
659
+ ```
660
+
661
+ ### Profile and Contact Updates
662
+
663
+ Handle profile picture and contact information changes:
664
+
665
+ ```typescript
666
+ // Profile picture changes
667
+ async function handlePicture(event) {
668
+ const { JID, Author, Remove, Timestamp } = event;
669
+ const target = JID.User;
670
+ const changer = Author.User;
671
+
672
+ if (Remove) {
673
+ console.log(`🗑️ ${changer} removed profile picture for ${target}`);
674
+ } else {
675
+ console.log(`🖼️ ${changer} updated profile picture for ${target}`);
676
+ }
677
+ }
678
+
679
+ // Contact info updates
680
+ async function handleContact(event) {
681
+ const { JID, Found, FullName, PushName, BusinessName } = event;
682
+ console.log(`👤 Contact info: ${FullName || PushName} (${JID.User})`);
683
+
684
+ if (BusinessName) {
685
+ console.log(`🏢 Business: ${BusinessName}`);
686
+ }
687
+
688
+ console.log(`✅ Found in WhatsApp: ${Found}`);
689
+ }
690
+
691
+ // Name changes
692
+ async function handlePushName(event) {
693
+ const { JID, OldPushName, NewPushName } = event;
694
+ console.log(
695
+ `📝 ${JID.User} changed name from "${OldPushName}" to "${NewPushName}"`
696
+ );
697
+ }
698
+ ```
699
+
700
+ ### Error Handling
701
+
702
+ Handle various error events:
703
+
704
+ ```typescript
705
+ // Undecryptable messages
706
+ async function handleUndecryptableMessage(event) {
707
+ const { Info, IsUnavailable, UnavailableType, DecryptFailMode } = event;
708
+ console.log(`❌ Failed to decrypt message from ${Info.Source.Sender.User}`);
709
+ console.log(
710
+ `📊 Unavailable: ${IsUnavailable}, Type: ${UnavailableType}, Mode: ${DecryptFailMode}`
711
+ );
712
+
713
+ // Log for debugging or request message retry
714
+ }
715
+
716
+ // Stream errors
717
+ async function handleStreamError(event) {
718
+ const { Code } = event;
719
+ console.log(`🚨 Stream error: ${Code}`);
720
+
721
+ // Handle specific error codes
722
+ switch (Code) {
723
+ case "conflict":
724
+ console.log("Another client connected with same credentials");
725
+ break;
726
+ case "stream:error":
727
+ console.log("General stream error occurred");
728
+ break;
729
+ default:
730
+ console.log("Unknown stream error");
731
+ }
732
+ }
733
+ ```
734
+
735
+ ### Complete Webhook Server Example
736
+
737
+ Here's a complete webhook server that handles all event types:
738
+
739
+ ```typescript
740
+ import express from "express";
741
+ import WuzapiClient, {
742
+ Message,
743
+ Receipt,
744
+ Presence,
745
+ EventGroupInfo,
746
+ } from "wuzapi";
747
+
748
+ const app = express();
749
+ app.use(express.json());
750
+
751
+ const client = new WuzapiClient({
752
+ apiUrl: process.env.WUZAPI_URL || "http://localhost:8080",
753
+ token: process.env.WUZAPI_TOKEN || "your-token",
754
+ });
755
+
756
+ // Event router
757
+ const eventHandlers = {
758
+ Message: handleMessage,
759
+ Receipt: handleReceipt,
760
+ Presence: handlePresence,
761
+ ChatPresence: handleChatPresence,
762
+ GroupInfo: handleGroupInfo,
763
+ JoinedGroup: handleJoinedGroup,
764
+ Connected: handleConnected,
765
+ Disconnected: handleDisconnected,
766
+ LoggedOut: handleLoggedOut,
767
+ QR: handleQR,
768
+ Picture: handlePicture,
769
+ Contact: handleContact,
770
+ PushName: handlePushName,
771
+ UndecryptableMessage: handleUndecryptableMessage,
772
+ StreamError: handleStreamError,
773
+ };
774
+
775
+ app.post("/webhook", async (req, res) => {
776
+ try {
777
+ const eventData = req.body;
778
+
779
+ // Log all events for debugging
780
+ console.log("📨 Webhook received:", JSON.stringify(eventData, null, 2));
781
+
782
+ // Route to appropriate handler based on event structure
783
+ for (const [eventType, handler] of Object.entries(eventHandlers)) {
784
+ if (isEventType(eventData, eventType)) {
785
+ await handler(eventData);
786
+ break;
787
+ }
788
+ }
789
+
790
+ res.status(200).json({ success: true });
791
+ } catch (error) {
792
+ console.error("❌ Webhook processing error:", error);
793
+ res.status(500).json({ error: error.message });
794
+ }
795
+ });
796
+
797
+ // Helper function to identify event types
798
+ function isEventType(event, type) {
799
+ switch (type) {
800
+ case "Message":
801
+ return event.Message && event.Info;
802
+ case "Receipt":
803
+ return event.MessageIDs && event.Type;
804
+ case "Presence":
805
+ return event.From && typeof event.Unavailable !== "undefined";
806
+ case "ChatPresence":
807
+ return event.State && event.MessageSource;
808
+ case "GroupInfo":
809
+ return event.GroupName && event.Participants;
810
+ case "QR":
811
+ return event.Codes;
812
+ // Add more type checks as needed
813
+ default:
814
+ return false;
815
+ }
816
+ }
817
+
818
+ // Health check
819
+ app.get("/health", (req, res) => {
820
+ res.json({ status: "healthy", timestamp: new Date().toISOString() });
821
+ });
822
+
823
+ // Initialize
824
+ async function initialize() {
825
+ try {
826
+ // Connect to WhatsApp with all events
827
+ await client.session.connect({
828
+ Subscribe: [
829
+ "Message",
830
+ "ReadReceipt",
831
+ "Presence",
832
+ "ChatPresence",
833
+ "GroupInfo",
834
+ "Contact",
835
+ "PushName",
836
+ "Picture",
837
+ "QR",
838
+ "Connected",
839
+ "Disconnected",
840
+ "LoggedOut",
841
+ "UndecryptableMessage",
842
+ "StreamError",
843
+ ],
844
+ Immediate: false,
845
+ });
846
+
847
+ // Set webhook
848
+ const webhookUrl =
849
+ process.env.WEBHOOK_URL || "http://localhost:3000/webhook";
850
+ await client.webhook.setWebhook(webhookUrl);
851
+
852
+ app.listen(3000, () => {
853
+ console.log("🚀 Webhook server running on port 3000");
854
+ console.log("🎉 Ready to receive WhatsApp events!");
855
+ });
856
+ } catch (error) {
857
+ console.error("❌ Initialization failed:", error);
858
+ process.exit(1);
859
+ }
860
+ }
861
+
862
+ initialize();
863
+ ```
864
+
865
+ ### Event Types Reference
866
+
867
+ All webhook events are fully typed. Import the specific event types you need:
868
+
869
+ ```typescript
870
+ import {
871
+ // Core message types
872
+ Message,
873
+ MessageEvent,
874
+ MessageContent,
875
+ getMessageContent,
876
+
877
+ // Specific message types
878
+ ImageMessage,
879
+ VideoMessage,
880
+ AudioMessage,
881
+ DocumentMessage,
882
+ LocationMessage,
883
+ ContactMessage,
884
+ StickerMessage,
885
+ ButtonsMessage,
886
+ ListMessage,
887
+ PollCreationMessage,
888
+ ReactionMessage,
889
+
890
+ // Event types
891
+ Receipt,
892
+ Presence,
893
+ ChatPresence,
894
+ EventGroupInfo,
895
+ EventContact,
896
+ QR,
897
+ Picture,
898
+ PushName,
899
+ Connected,
900
+ Disconnected,
901
+ LoggedOut,
902
+ UndecryptableMessage,
903
+ StreamError,
904
+ WhatsAppEvent,
905
+ } from "wuzapi";
906
+
907
+ // Type-safe event handling with full message structure
908
+ async function handleTypedMessage(event: MessageEvent) {
909
+ const messageContent = getMessageContent(event.Message);
910
+
911
+ if (messageContent?.type === "image") {
912
+ // TypeScript knows this is an ImageMessage
913
+ const imageMsg: ImageMessage = messageContent.content;
914
+ console.log(`Image dimensions: ${imageMsg.width}x${imageMsg.height}`);
915
+ console.log(`File size: ${imageMsg.fileLength} bytes`);
916
+
917
+ // Full type safety and autocompletion
918
+ if (imageMsg.caption) {
919
+ console.log(`Caption: ${imageMsg.caption}`);
920
+ }
921
+ }
922
+
923
+ if (messageContent?.type === "buttons") {
924
+ // TypeScript knows this is a ButtonsMessage
925
+ const buttonsMsg: ButtonsMessage = messageContent.content;
926
+ console.log(`Button message: ${buttonsMsg.contentText}`);
927
+
928
+ buttonsMsg.buttons?.forEach((button, index) => {
929
+ console.log(`Button ${index + 1}: ${button.buttonText?.displayText}`);
930
+ });
931
+ }
932
+ }
933
+
934
+ // Helper function to handle all message types generically
935
+ function processMessageContent(content: MessageContent) {
936
+ switch (content.type) {
937
+ case "text":
938
+ // content.content is string
939
+ return content.content.toUpperCase();
940
+
941
+ case "image":
942
+ // content.content is ImageMessage
943
+ return `Image: ${content.content.caption || "No caption"}`;
944
+
945
+ case "video":
946
+ // content.content is VideoMessage
947
+ return `Video (${content.content.seconds}s): ${
948
+ content.content.caption || "No caption"
949
+ }`;
950
+
951
+ // TypeScript ensures you handle all possible types
952
+ default:
953
+ return `Unknown message type: ${content.type}`;
954
+ }
955
+ }
956
+ ```
957
+
958
+ ### Advanced Message Type Handling
959
+
960
+ For complex message processing, you can work directly with the typed message structures:
961
+
962
+ ```typescript
963
+ import {
964
+ Message,
965
+ ExtendedTextMessage,
966
+ ButtonsMessage,
967
+ ContextInfo,
968
+ } from "wuzapi";
969
+
970
+ // Check for extended text with link preview
971
+ function hasLinkPreview(message: Message): boolean {
972
+ return !!message.extendedTextMessage?.canonicalUrl;
973
+ }
974
+
975
+ // Extract context info from any message
976
+ function getMessageContext(message: Message): ContextInfo | undefined {
977
+ // Check various message types for context info
978
+ return (
979
+ message.extendedTextMessage?.contextInfo ||
980
+ message.imageMessage?.contextInfo ||
981
+ message.videoMessage?.contextInfo ||
982
+ message.audioMessage?.contextInfo
983
+ );
984
+ }
985
+
986
+ // Process interactive messages
987
+ function handleInteractiveMessage(message: Message) {
988
+ if (message.buttonsMessage) {
989
+ const btns = message.buttonsMessage;
990
+ console.log(`Interactive message: ${btns.contentText}`);
991
+
992
+ btns.buttons?.forEach((button) => {
993
+ if (button.type === ButtonType.RESPONSE) {
994
+ console.log(`Response button: ${button.buttonText?.displayText}`);
995
+ } else if (button.type === ButtonType.NATIVE_FLOW) {
996
+ console.log(`Native flow: ${button.nativeFlowInfo?.name}`);
997
+ }
998
+ });
999
+ }
1000
+
1001
+ if (message.listMessage) {
1002
+ const list = message.listMessage;
1003
+ console.log(`List: ${list.title}`);
1004
+
1005
+ list.sections?.forEach((section) => {
1006
+ console.log(`Section: ${section.title}`);
1007
+ section.rows?.forEach((row) => {
1008
+ console.log(`- ${row.title}: ${row.description}`);
1009
+ });
1010
+ });
1011
+ }
1012
+ }
1013
+ ```
1014
+
300
1015
  ## Error Handling
301
1016
 
302
1017
  The library provides comprehensive error handling with detailed error information:
@@ -401,6 +1116,12 @@ if (isConnected) {
401
1116
 
402
1117
  ## Examples
403
1118
 
1119
+ For complete working examples, check the `examples/` directory:
1120
+
1121
+ - **`basic-usage.js`** - Basic client setup and usage
1122
+ - **`chatbot-example.js`** - Simple chatbot with command handling
1123
+ - **`webhook-events-example.js`** - Comprehensive webhook event handling
1124
+
404
1125
  ### Complete Chat Bot Example
405
1126
 
406
1127
  ```typescript