whatsapp-cloud-api-ruby 1.0.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.
data/README.md ADDED
@@ -0,0 +1,735 @@
1
+ # WhatsApp Cloud API Ruby SDK
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/whatsapp-cloud-api-ruby.svg)](https://badge.fury.io/rb/whatsapp-cloud-api-ruby)
4
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.7.0-red.svg)](https://www.ruby-lang.org/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ A comprehensive Ruby client library for the [WhatsApp Business Cloud API](https://developers.facebook.com/docs/whatsapp/cloud-api/). This SDK provides a complete interface for sending messages, managing media, templates, and more, with built-in error handling, retry logic, and debug capabilities.
8
+
9
+ ## Features
10
+
11
+ - 🚀 **Complete API Coverage**: All WhatsApp Cloud API endpoints supported
12
+ - 📱 **Rich Message Types**: Text, media, templates, interactive messages, and more
13
+ - 🔐 **Dual Authentication**: Meta Graph API and Kapso Proxy support
14
+ - 🛡️ **Smart Error Handling**: Comprehensive error categorization and retry logic
15
+ - 📊 **Advanced Features**: Message history, analytics, and contact management (via Kapso)
16
+ - 🔍 **Debug Support**: Detailed logging and request/response tracing
17
+ - 📚 **Type Safety**: Structured response objects and validation
18
+ - ⚡ **Performance**: HTTP connection pooling and efficient request handling
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ ```ruby
25
+ gem 'whatsapp-cloud-api-ruby'
26
+ ```
27
+
28
+ And then execute:
29
+
30
+ ```bash
31
+ $ bundle install
32
+ ```
33
+
34
+ Or install it yourself as:
35
+
36
+ ```bash
37
+ $ gem install whatsapp-cloud-api-ruby
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ### Basic Setup
43
+
44
+ ```ruby
45
+ require 'whatsapp_cloud_api'
46
+
47
+ # Initialize client with Meta Graph API access token
48
+ client = WhatsAppCloudApi::Client.new(
49
+ access_token: 'your_access_token'
50
+ )
51
+
52
+ # Send a text message
53
+ response = client.messages.send_text(
54
+ phone_number_id: 'your_phone_number_id',
55
+ to: '+1234567890',
56
+ body: 'Hello from Ruby!'
57
+ )
58
+
59
+ puts "Message sent: #{response.messages.first.id}"
60
+ ```
61
+
62
+ ### Using Kapso Proxy (for enhanced features)
63
+
64
+ ```ruby
65
+ # Initialize client with Kapso API key for enhanced features
66
+ kapso_client = WhatsAppCloudApi::Client.new(
67
+ kapso_api_key: 'your_kapso_api_key',
68
+ base_url: 'https://app.kapso.ai/api/meta'
69
+ )
70
+
71
+ # Access message history and analytics
72
+ messages = kapso_client.messages.query(
73
+ phone_number_id: 'your_phone_number_id',
74
+ direction: 'inbound',
75
+ limit: 10
76
+ )
77
+ ```
78
+
79
+ ## API Reference
80
+
81
+ ### Messages
82
+
83
+ Send various types of messages with the Messages resource:
84
+
85
+ #### Text Messages
86
+
87
+ ```ruby
88
+ # Simple text message
89
+ client.messages.send_text(
90
+ phone_number_id: 'phone_id',
91
+ to: '+1234567890',
92
+ body: 'Hello World!'
93
+ )
94
+
95
+ # Text with URL preview
96
+ client.messages.send_text(
97
+ phone_number_id: 'phone_id',
98
+ to: '+1234567890',
99
+ body: 'Check this out: https://example.com',
100
+ preview_url: true
101
+ )
102
+ ```
103
+
104
+ #### Media Messages
105
+
106
+ ```ruby
107
+ # Send image
108
+ client.messages.send_image(
109
+ phone_number_id: 'phone_id',
110
+ to: '+1234567890',
111
+ image: {
112
+ link: 'https://example.com/image.jpg',
113
+ caption: 'Beautiful sunset'
114
+ }
115
+ )
116
+
117
+ # Send document
118
+ client.messages.send_document(
119
+ phone_number_id: 'phone_id',
120
+ to: '+1234567890',
121
+ document: {
122
+ id: 'media_id', # or link: 'https://...'
123
+ filename: 'report.pdf',
124
+ caption: 'Monthly report'
125
+ }
126
+ )
127
+
128
+ # Send audio
129
+ client.messages.send_audio(
130
+ phone_number_id: 'phone_id',
131
+ to: '+1234567890',
132
+ audio: { id: 'audio_media_id' }
133
+ )
134
+
135
+ # Send video
136
+ client.messages.send_video(
137
+ phone_number_id: 'phone_id',
138
+ to: '+1234567890',
139
+ video: {
140
+ link: 'https://example.com/video.mp4',
141
+ caption: 'Tutorial video'
142
+ }
143
+ )
144
+ ```
145
+
146
+ #### Interactive Messages
147
+
148
+ ```ruby
149
+ # Button interactive message
150
+ client.messages.send_interactive_buttons(
151
+ phone_number_id: 'phone_id',
152
+ to: '+1234567890',
153
+ body_text: 'Choose an option:',
154
+ buttons: [
155
+ {
156
+ type: 'reply',
157
+ reply: {
158
+ id: 'option_1',
159
+ title: 'Option 1'
160
+ }
161
+ },
162
+ {
163
+ type: 'reply',
164
+ reply: {
165
+ id: 'option_2',
166
+ title: 'Option 2'
167
+ }
168
+ }
169
+ ],
170
+ header: {
171
+ type: 'text',
172
+ text: 'Menu'
173
+ }
174
+ )
175
+
176
+ # List interactive message
177
+ client.messages.send_interactive_list(
178
+ phone_number_id: 'phone_id',
179
+ to: '+1234567890',
180
+ body_text: 'Please select from the list:',
181
+ button_text: 'View Options',
182
+ sections: [
183
+ {
184
+ title: 'Section 1',
185
+ rows: [
186
+ {
187
+ id: 'item_1',
188
+ title: 'Item 1',
189
+ description: 'Description 1'
190
+ }
191
+ ]
192
+ }
193
+ ]
194
+ )
195
+ ```
196
+
197
+ #### Template Messages
198
+
199
+ ```ruby
200
+ # Simple template
201
+ client.messages.send_template(
202
+ phone_number_id: 'phone_id',
203
+ to: '+1234567890',
204
+ name: 'hello_world',
205
+ language: 'en_US'
206
+ )
207
+
208
+ # Template with parameters
209
+ client.messages.send_template(
210
+ phone_number_id: 'phone_id',
211
+ to: '+1234567890',
212
+ name: 'appointment_reminder',
213
+ language: 'en_US',
214
+ components: [
215
+ {
216
+ type: 'body',
217
+ parameters: [
218
+ { type: 'text', text: 'John Doe' },
219
+ { type: 'text', text: 'Tomorrow at 2 PM' }
220
+ ]
221
+ }
222
+ ]
223
+ )
224
+ ```
225
+
226
+ #### Message Reactions
227
+
228
+ ```ruby
229
+ # Add reaction
230
+ client.messages.send_reaction(
231
+ phone_number_id: 'phone_id',
232
+ to: '+1234567890',
233
+ message_id: 'message_to_react_to',
234
+ emoji: '👍'
235
+ )
236
+
237
+ # Remove reaction
238
+ client.messages.send_reaction(
239
+ phone_number_id: 'phone_id',
240
+ to: '+1234567890',
241
+ message_id: 'message_to_react_to',
242
+ emoji: nil
243
+ )
244
+ ```
245
+
246
+ #### Message Status
247
+
248
+ ```ruby
249
+ # Mark message as read
250
+ client.messages.mark_read(
251
+ phone_number_id: 'phone_id',
252
+ message_id: 'message_id'
253
+ )
254
+
255
+ # Send typing indicator
256
+ client.messages.send_typing_indicator(
257
+ phone_number_id: 'phone_id',
258
+ to: '+1234567890'
259
+ )
260
+ ```
261
+
262
+ ### Media Management
263
+
264
+ Handle media uploads, downloads, and management:
265
+
266
+ ```ruby
267
+ # Upload media
268
+ upload_response = client.media.upload(
269
+ phone_number_id: 'phone_id',
270
+ type: 'image',
271
+ file: '/path/to/image.jpg'
272
+ )
273
+
274
+ media_id = upload_response.id
275
+
276
+ # Get media metadata
277
+ metadata = client.media.get(media_id: media_id)
278
+ puts "File size: #{metadata.file_size} bytes"
279
+ puts "MIME type: #{metadata.mime_type}"
280
+
281
+ # Download media
282
+ content = client.media.download(
283
+ media_id: media_id,
284
+ as: :binary
285
+ )
286
+
287
+ # Save media to file
288
+ client.media.save_to_file(
289
+ media_id: media_id,
290
+ filepath: '/path/to/save/file.jpg'
291
+ )
292
+
293
+ # Delete media
294
+ client.media.delete(media_id: media_id)
295
+ ```
296
+
297
+ ### Template Management
298
+
299
+ Create, manage, and use message templates:
300
+
301
+ ```ruby
302
+ # List templates
303
+ templates = client.templates.list(
304
+ business_account_id: 'your_business_id',
305
+ status: 'APPROVED'
306
+ )
307
+
308
+ # Create marketing template
309
+ template_data = client.templates.build_marketing_template(
310
+ name: 'summer_sale',
311
+ language: 'en_US',
312
+ body: 'Hi {{1}}! Our summer sale is here with {{2}} off!',
313
+ header: {
314
+ type: 'HEADER',
315
+ format: 'TEXT',
316
+ text: 'Summer Sale 🌞'
317
+ },
318
+ footer: 'Limited time offer',
319
+ buttons: [
320
+ {
321
+ type: 'URL',
322
+ text: 'Shop Now',
323
+ url: 'https://shop.example.com'
324
+ }
325
+ ],
326
+ body_example: {
327
+ body_text: [['John', '25%']]
328
+ }
329
+ )
330
+
331
+ response = client.templates.create(
332
+ business_account_id: 'your_business_id',
333
+ **template_data
334
+ )
335
+
336
+ # Create authentication template
337
+ auth_template = client.templates.build_authentication_template(
338
+ name: 'verify_code',
339
+ language: 'en_US',
340
+ ttl_seconds: 300
341
+ )
342
+
343
+ client.templates.create(
344
+ business_account_id: 'your_business_id',
345
+ **auth_template
346
+ )
347
+
348
+ # Delete template
349
+ client.templates.delete(
350
+ business_account_id: 'your_business_id',
351
+ name: 'old_template',
352
+ language: 'en_US'
353
+ )
354
+ ```
355
+
356
+ ### Advanced Features (Kapso Proxy)
357
+
358
+ Access enhanced features with Kapso proxy:
359
+
360
+ ```ruby
361
+ # Initialize Kapso client
362
+ kapso_client = WhatsAppCloudApi::Client.new(
363
+ kapso_api_key: 'your_kapso_key',
364
+ base_url: 'https://app.kapso.ai/api/meta'
365
+ )
366
+
367
+ # Message history
368
+ messages = kapso_client.messages.query(
369
+ phone_number_id: 'phone_id',
370
+ direction: 'inbound',
371
+ since: '2024-01-01T00:00:00Z',
372
+ limit: 50
373
+ )
374
+
375
+ # Conversation management
376
+ conversations = kapso_client.conversations.list(
377
+ phone_number_id: 'phone_id',
378
+ status: 'active'
379
+ )
380
+
381
+ conversation = kapso_client.conversations.get(
382
+ conversation_id: conversations.data.first.id
383
+ )
384
+
385
+ # Update conversation status
386
+ kapso_client.conversations.update_status(
387
+ conversation_id: conversation.id,
388
+ status: 'archived'
389
+ )
390
+
391
+ # Contact management
392
+ contacts = kapso_client.contacts.list(
393
+ phone_number_id: 'phone_id',
394
+ limit: 100
395
+ )
396
+
397
+ # Update contact metadata
398
+ kapso_client.contacts.update(
399
+ phone_number_id: 'phone_id',
400
+ wa_id: 'contact_wa_id',
401
+ metadata: {
402
+ tags: ['premium', 'customer'],
403
+ source: 'website'
404
+ }
405
+ )
406
+
407
+ # Search contacts
408
+ results = kapso_client.contacts.search(
409
+ phone_number_id: 'phone_id',
410
+ query: 'john',
411
+ search_in: ['profile_name', 'phone_number']
412
+ )
413
+ ```
414
+
415
+ ## Configuration
416
+
417
+ ### Global Configuration
418
+
419
+ ```ruby
420
+ WhatsAppCloudApi.configure do |config|
421
+ config.debug = true
422
+ config.timeout = 60
423
+ config.open_timeout = 10
424
+ config.max_retries = 3
425
+ config.retry_delay = 1.0
426
+ end
427
+ ```
428
+
429
+ ### Client Configuration
430
+
431
+ ```ruby
432
+ client = WhatsAppCloudApi::Client.new(
433
+ access_token: 'token',
434
+ debug: true,
435
+ timeout: 30,
436
+ logger: Logger.new('whatsapp.log')
437
+ )
438
+ ```
439
+
440
+ ### Debug Logging
441
+
442
+ Enable debug logging to see detailed HTTP requests and responses:
443
+
444
+ ```ruby
445
+ # Enable debug mode
446
+ client = WhatsAppCloudApi::Client.new(
447
+ access_token: 'token',
448
+ debug: true
449
+ )
450
+
451
+ # Custom logger
452
+ logger = Logger.new(STDOUT)
453
+ logger.level = Logger::DEBUG
454
+
455
+ client = WhatsAppCloudApi::Client.new(
456
+ access_token: 'token',
457
+ logger: logger
458
+ )
459
+ ```
460
+
461
+ ## Error Handling
462
+
463
+ The SDK provides comprehensive error handling with detailed categorization:
464
+
465
+ ```ruby
466
+ begin
467
+ client.messages.send_text(
468
+ phone_number_id: 'phone_id',
469
+ to: 'invalid_number',
470
+ body: 'Test'
471
+ )
472
+ rescue WhatsAppCloudApi::Errors::GraphApiError => e
473
+ puts "Error: #{e.message}"
474
+ puts "Category: #{e.category}"
475
+ puts "HTTP Status: #{e.http_status}"
476
+ puts "Code: #{e.code}"
477
+
478
+ # Check error type
479
+ case e.category
480
+ when :authorization
481
+ puts "Authentication failed - check your access token"
482
+ when :parameter
483
+ puts "Invalid parameter - check phone number format"
484
+ when :throttling
485
+ puts "Rate limited - wait before retrying"
486
+ if e.retry_hint[:retry_after_ms]
487
+ sleep(e.retry_hint[:retry_after_ms] / 1000.0)
488
+ end
489
+ when :template
490
+ puts "Template error - check template name and parameters"
491
+ when :media
492
+ puts "Media error - check file format and size"
493
+ end
494
+
495
+ # Check retry recommendations
496
+ case e.retry_hint[:action]
497
+ when :retry
498
+ puts "Safe to retry this request"
499
+ when :retry_after
500
+ puts "Retry after specified delay: #{e.retry_hint[:retry_after_ms]}ms"
501
+ when :do_not_retry
502
+ puts "Do not retry - permanent error"
503
+ when :fix_and_retry
504
+ puts "Fix the request and retry"
505
+ when :refresh_token
506
+ puts "Access token needs to be refreshed"
507
+ end
508
+ end
509
+ ```
510
+
511
+ ### Error Categories
512
+
513
+ - `:authorization` - Authentication and token errors
514
+ - `:permission` - Permission and access errors
515
+ - `:parameter` - Invalid parameters or format errors
516
+ - `:throttling` - Rate limiting errors
517
+ - `:template` - Template-related errors
518
+ - `:media` - Media upload/download errors
519
+ - `:phone_registration` - Phone number registration errors
520
+ - `:integrity` - Message integrity errors
521
+ - `:business_eligibility` - Business account eligibility errors
522
+ - `:reengagement_window` - 24-hour messaging window errors
523
+ - `:waba_config` - WhatsApp Business Account configuration errors
524
+ - `:flow` - WhatsApp Flow errors
525
+ - `:synchronization` - Data synchronization errors
526
+ - `:server` - Server-side errors
527
+ - `:unknown` - Unclassified errors
528
+
529
+ ### Automatic Retry Logic
530
+
531
+ ```ruby
532
+ def send_with_retry(client, max_retries = 3)
533
+ retries = 0
534
+
535
+ begin
536
+ client.messages.send_text(
537
+ phone_number_id: 'phone_id',
538
+ to: '+1234567890',
539
+ body: 'Test message'
540
+ )
541
+ rescue WhatsAppCloudApi::Errors::GraphApiError => e
542
+ retries += 1
543
+
544
+ case e.retry_hint[:action]
545
+ when :retry
546
+ if retries <= max_retries
547
+ sleep(retries * 2) # Exponential backoff
548
+ retry
549
+ end
550
+ when :retry_after
551
+ if e.retry_hint[:retry_after_ms] && retries <= max_retries
552
+ sleep(e.retry_hint[:retry_after_ms] / 1000.0)
553
+ retry
554
+ end
555
+ end
556
+
557
+ raise # Re-raise if no retry
558
+ end
559
+ end
560
+ ```
561
+
562
+ ## Webhook Handling
563
+
564
+ Handle incoming webhooks from WhatsApp:
565
+
566
+ ```ruby
567
+ # Verify webhook signature
568
+ def verify_webhook_signature(payload, signature, app_secret)
569
+ require 'openssl'
570
+
571
+ sig_hash = signature.sub('sha256=', '')
572
+ expected_sig = OpenSSL::HMAC.hexdigest('sha256', app_secret, payload)
573
+
574
+ sig_hash == expected_sig
575
+ end
576
+
577
+ # In your webhook endpoint
578
+ def handle_webhook(request)
579
+ payload = request.body.read
580
+ signature = request.headers['X-Hub-Signature-256']
581
+
582
+ unless verify_webhook_signature(payload, signature, ENV['WHATSAPP_APP_SECRET'])
583
+ return [401, {}, ['Unauthorized']]
584
+ end
585
+
586
+ webhook_data = JSON.parse(payload)
587
+
588
+ # Process webhook data
589
+ webhook_data['entry'].each do |entry|
590
+ entry['changes'].each do |change|
591
+ if change['field'] == 'messages'
592
+ messages = change['value']['messages'] || []
593
+ messages.each do |message|
594
+ handle_incoming_message(message)
595
+ end
596
+ end
597
+ end
598
+ end
599
+
600
+ [200, {}, ['OK']]
601
+ end
602
+
603
+ def handle_incoming_message(message)
604
+ case message['type']
605
+ when 'text'
606
+ puts "Received text: #{message['text']['body']}"
607
+ when 'image'
608
+ puts "Received image: #{message['image']['id']}"
609
+ when 'interactive'
610
+ puts "Received interactive response: #{message['interactive']}"
611
+ end
612
+ end
613
+ ```
614
+
615
+ ## Testing
616
+
617
+ Run the test suite:
618
+
619
+ ```bash
620
+ # Install development dependencies
621
+ bundle install
622
+
623
+ # Run tests
624
+ bundle exec rspec
625
+
626
+ # Run tests with coverage
627
+ bundle exec rspec --format documentation
628
+
629
+ # Run rubocop for style checking
630
+ bundle exec rubocop
631
+ ```
632
+
633
+ ### Testing with VCR
634
+
635
+ The SDK includes VCR cassettes for testing without making real API calls:
636
+
637
+ ```ruby
638
+ # spec/spec_helper.rb
639
+ require 'vcr'
640
+
641
+ VCR.configure do |config|
642
+ config.cassette_library_dir = 'spec/vcr_cassettes'
643
+ config.hook_into :webmock
644
+ config.configure_rspec_metadata!
645
+
646
+ # Filter sensitive data
647
+ config.filter_sensitive_data('<ACCESS_TOKEN>') { ENV['WHATSAPP_ACCESS_TOKEN'] }
648
+ config.filter_sensitive_data('<PHONE_NUMBER_ID>') { ENV['PHONE_NUMBER_ID'] }
649
+ end
650
+
651
+ # In your tests
652
+ RSpec.describe 'Messages' do
653
+ it 'sends text message', :vcr do
654
+ client = WhatsAppCloudApi::Client.new(access_token: 'test_token')
655
+
656
+ response = client.messages.send_text(
657
+ phone_number_id: 'test_phone_id',
658
+ to: '+1234567890',
659
+ body: 'Test message'
660
+ )
661
+
662
+ expect(response.messages.first.id).to be_present
663
+ end
664
+ end
665
+ ```
666
+
667
+ ## Examples
668
+
669
+ See the [examples](examples/) directory for comprehensive usage examples:
670
+
671
+ - [Basic Messaging](examples/basic_messaging.rb) - Text, media, and template messages
672
+ - [Media Management](examples/media_management.rb) - Upload, download, and manage media
673
+ - [Template Management](examples/template_management.rb) - Create and manage templates
674
+ - [Advanced Features](examples/advanced_features.rb) - Kapso proxy features and analytics
675
+
676
+ ## Requirements
677
+
678
+ - Ruby >= 2.7.0
679
+ - Faraday >= 2.0
680
+ - A WhatsApp Business Account with Cloud API access
681
+ - Valid access token from Meta or Kapso API key
682
+
683
+ ## Contributing
684
+
685
+ Bug reports and pull requests are welcome on GitHub at https://github.com/gokapso/whatsapp-cloud-api-ruby.
686
+
687
+ ### Development Setup
688
+
689
+ ```bash
690
+ git clone https://github.com/gokapso/whatsapp-cloud-api-ruby.git
691
+ cd whatsapp-cloud-api-ruby
692
+ bundle install
693
+ ```
694
+
695
+ ### Running Tests
696
+
697
+ ```bash
698
+ # Run all tests
699
+ bundle exec rspec
700
+
701
+ # Run specific test file
702
+ bundle exec rspec spec/client_spec.rb
703
+
704
+ # Run with coverage
705
+ COVERAGE=true bundle exec rspec
706
+ ```
707
+
708
+ ### Code Style
709
+
710
+ ```bash
711
+ # Check style
712
+ bundle exec rubocop
713
+
714
+ # Auto-fix issues
715
+ bundle exec rubocop -A
716
+ ```
717
+
718
+ ## License
719
+
720
+ This gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
721
+
722
+ ## Support
723
+
724
+ - 📖 [WhatsApp Cloud API Documentation](https://developers.facebook.com/docs/whatsapp/cloud-api/)
725
+ - 🌐 [Kapso Platform](https://kapso.ai/) for enhanced features
726
+ - 🐛 [Issue Tracker](https://github.com/PabloB07/whatsapp-cloud-api-ruby/issues)
727
+ - 📧 Email: support@kapso.ai
728
+
729
+ ## Changelog
730
+
731
+ See [CHANGELOG.md](CHANGELOG.md) for version history and updates.
732
+
733
+ ---
734
+
735
+ Built with ❤️ for the [Kapso](https://kapso.ai) team