kapso-client-ruby 1.0.0 → 1.0.2

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +81 -81
  3. data/CHANGELOG.md +262 -91
  4. data/Gemfile +20 -20
  5. data/RAILS_INTEGRATION.md +478 -0
  6. data/README.md +1053 -734
  7. data/Rakefile +40 -40
  8. data/TEMPLATE_TOOLS_GUIDE.md +120 -120
  9. data/WHATSAPP_24_HOUR_GUIDE.md +133 -133
  10. data/examples/advanced_features.rb +352 -349
  11. data/examples/advanced_messaging.rb +241 -0
  12. data/examples/basic_messaging.rb +139 -136
  13. data/examples/enhanced_interactive.rb +400 -0
  14. data/examples/flows_usage.rb +307 -0
  15. data/examples/interactive_messages.rb +343 -0
  16. data/examples/media_management.rb +256 -253
  17. data/examples/rails/jobs.rb +388 -0
  18. data/examples/rails/models.rb +240 -0
  19. data/examples/rails/notifications_controller.rb +227 -0
  20. data/examples/template_management.rb +393 -390
  21. data/kapso-ruby-logo.jpg +0 -0
  22. data/lib/kapso_client_ruby/client.rb +321 -316
  23. data/lib/kapso_client_ruby/errors.rb +348 -329
  24. data/lib/kapso_client_ruby/rails/generators/install_generator.rb +76 -0
  25. data/lib/kapso_client_ruby/rails/generators/templates/env.erb +21 -0
  26. data/lib/kapso_client_ruby/rails/generators/templates/initializer.rb.erb +33 -0
  27. data/lib/kapso_client_ruby/rails/generators/templates/message_service.rb.erb +138 -0
  28. data/lib/kapso_client_ruby/rails/generators/templates/webhook_controller.rb.erb +62 -0
  29. data/lib/kapso_client_ruby/rails/railtie.rb +55 -0
  30. data/lib/kapso_client_ruby/rails/service.rb +189 -0
  31. data/lib/kapso_client_ruby/rails/tasks.rake +167 -0
  32. data/lib/kapso_client_ruby/resources/calls.rb +172 -172
  33. data/lib/kapso_client_ruby/resources/contacts.rb +190 -190
  34. data/lib/kapso_client_ruby/resources/conversations.rb +103 -103
  35. data/lib/kapso_client_ruby/resources/flows.rb +382 -0
  36. data/lib/kapso_client_ruby/resources/media.rb +205 -205
  37. data/lib/kapso_client_ruby/resources/messages.rb +760 -380
  38. data/lib/kapso_client_ruby/resources/phone_numbers.rb +85 -85
  39. data/lib/kapso_client_ruby/resources/templates.rb +283 -283
  40. data/lib/kapso_client_ruby/types.rb +348 -262
  41. data/lib/kapso_client_ruby/version.rb +5 -5
  42. data/lib/kapso_client_ruby.rb +75 -68
  43. data/scripts/.env.example +17 -17
  44. data/scripts/kapso_template_finder.rb +91 -91
  45. data/scripts/sdk_setup.rb +404 -404
  46. data/scripts/test.rb +60 -60
  47. metadata +24 -3
data/README.md CHANGED
@@ -1,735 +1,1054 @@
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
-
1
+ <div align="center">
2
+ <img src="kapso-ruby-logo.jpg" alt="Kapso Ruby Client" width="400">
3
+ </div>
4
+
5
+ # Kapso API Ruby SDK
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/kapso-client-ruby.svg)](https://badge.fury.io/rb/kapso-client-ruby)
8
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.7.0-red.svg)](https://www.ruby-lang.org/)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
+
11
+ 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.
12
+
13
+ ## Features
14
+
15
+ - 🚀 **Complete API Coverage**: All Kapso Cloud API endpoints supported
16
+ - 📱 **Rich Message Types**: Text, media, templates, interactive messages, and more
17
+ - 🔐 **Dual Authentication**: Meta Graph API and Kapso Proxy support
18
+ - 🛡️ **Smart Error Handling**: Comprehensive error categorization and retry logic
19
+ - 📊 **Advanced Features**: Message history, analytics, and contact management (via Kapso)
20
+ - 🔍 **Debug Support**: Detailed logging and request/response tracing
21
+ - 📚 **Type Safety**: Structured response objects and validation
22
+ - **Performance**: HTTP connection pooling and efficient request handling
23
+ - 🛤️ **Rails Integration**: First-class Rails support with generators, service classes, and background jobs
24
+
25
+ ## Installation
26
+
27
+ Add this line to your application's Gemfile:
28
+
29
+ ```ruby
30
+ gem 'kapso-client-ruby'
31
+ ```
32
+
33
+ And then execute:
34
+
35
+ ```bash
36
+ $ bundle install
37
+ ```
38
+
39
+ Or install it yourself as:
40
+
41
+ ```bash
42
+ $ gem install kapso-client-ruby
43
+ ```
44
+
45
+ ### Rails Integration
46
+
47
+ For Rails applications, use the built-in generator to set up everything automatically:
48
+
49
+ ```bash
50
+ rails generate kapso_client_ruby:install
51
+ ```
52
+
53
+ This creates:
54
+ - Configuration initializer
55
+ - Webhook controller
56
+ - Service class for messaging
57
+ - Background job examples
58
+ - Routes for webhooks
59
+
60
+ See the [Rails Integration Guide](RAILS_INTEGRATION.md) for detailed Rails-specific documentation.
61
+
62
+ ## Quick Start
63
+
64
+ ### Basic Setup
65
+
66
+ ```ruby
67
+ require 'kapso_client_api'
68
+
69
+ # Initialize client with Meta Graph API access token
70
+ client = KapsoClientRuby::Client.new(
71
+ access_token: 'your_access_token'
72
+ )
73
+
74
+ # Send a text message
75
+ response = client.messages.send_text(
76
+ phone_number_id: 'your_phone_number_id',
77
+ to: '+1234567890',
78
+ body: 'Hello from Ruby!'
79
+ )
80
+
81
+ puts "Message sent: #{response.messages.first.id}"
82
+ ```
83
+
84
+ ### Using Kapso Proxy (for enhanced features)
85
+
86
+ ```ruby
87
+ # Initialize client with Kapso API key for enhanced features
88
+ kapso_client = KapsoClientRuby::Client.new(
89
+ kapso_api_key: 'your_kapso_api_key',
90
+ base_url: 'https://app.kapso.ai/api/meta'
91
+ )
92
+
93
+ # Access message history and analytics
94
+ messages = kapso_client.messages.query(
95
+ phone_number_id: 'your_phone_number_id',
96
+ direction: 'inbound',
97
+ limit: 10
98
+ )
99
+ ```
100
+
101
+ ## API Reference
102
+
103
+ ### Flows
104
+
105
+ Create and manage interactive WhatsApp Flows for rich data collection:
106
+
107
+ #### Create and Deploy a Flow
108
+
109
+ ```ruby
110
+ # Idempotent deployment (recommended)
111
+ deployment = client.flows.deploy(
112
+ business_account_id: 'your_business_id',
113
+ name: 'appointment_booking',
114
+ categories: ['APPOINTMENT_BOOKING'],
115
+ flow_json: {
116
+ version: '3.0',
117
+ screens: [
118
+ {
119
+ id: 'APPOINTMENT',
120
+ title: 'Book Appointment',
121
+ layout: {
122
+ type: 'SingleColumnLayout',
123
+ children: [
124
+ {
125
+ type: 'Form',
126
+ name: 'appointment_form',
127
+ children: [
128
+ {
129
+ type: 'TextInput',
130
+ name: 'customer_name',
131
+ label: 'Full Name',
132
+ required: true
133
+ },
134
+ {
135
+ type: 'DatePicker',
136
+ name: 'appointment_date',
137
+ label: 'Preferred Date',
138
+ required: true
139
+ },
140
+ {
141
+ type: 'Footer',
142
+ label: 'Submit',
143
+ on_click_action: {
144
+ name: 'complete',
145
+ payload: {
146
+ customer_name: '${form.customer_name}',
147
+ appointment_date: '${form.appointment_date}'
148
+ }
149
+ }
150
+ }
151
+ ]
152
+ }
153
+ ]
154
+ }
155
+ }
156
+ ]
157
+ }
158
+ )
159
+
160
+ puts "Flow ID: #{deployment[:id]}"
161
+ ```
162
+
163
+ #### Send a Flow Message
164
+
165
+ ```ruby
166
+ require 'securerandom'
167
+
168
+ client.messages.send_flow(
169
+ phone_number_id: 'phone_id',
170
+ to: '+1234567890',
171
+ flow_id: 'your_flow_id',
172
+ flow_cta: 'Book Appointment',
173
+ flow_token: SecureRandom.uuid,
174
+ header: {
175
+ type: 'text',
176
+ text: 'Appointment Booking'
177
+ },
178
+ body_text: 'Book your appointment in just a few taps!',
179
+ footer_text: 'Available slots fill up fast'
180
+ )
181
+ ```
182
+
183
+ #### Handle Flow Webhooks
184
+
185
+ ```ruby
186
+ # In your webhook endpoint
187
+ def handle_flow_webhook(encrypted_request)
188
+ private_key = OpenSSL::PKey::RSA.new(File.read('private_key.pem'))
189
+
190
+ # Decrypt incoming Flow event
191
+ flow_event = client.flows.receive_flow_event(
192
+ encrypted_request: encrypted_request,
193
+ private_key: private_key
194
+ )
195
+
196
+ # Process form data
197
+ case flow_event.action
198
+ when 'INIT'
199
+ response_data = {
200
+ version: flow_event.version,
201
+ screen: 'APPOINTMENT',
202
+ data: { available_dates: ['2024-01-15', '2024-01-16'] }
203
+ }
204
+ when 'data_exchange'
205
+ # Save appointment data
206
+ customer_name = flow_event.data['customer_name']
207
+ appointment_date = flow_event.data['appointment_date']
208
+
209
+ response_data = {
210
+ version: flow_event.version,
211
+ screen: 'SUCCESS',
212
+ data: { success: true }
213
+ }
214
+ end
215
+
216
+ # Encrypt and return response
217
+ client.flows.respond_to_flow(
218
+ response_data: response_data,
219
+ private_key: private_key
220
+ )
221
+ end
222
+ ```
223
+
224
+ #### Manage Flows
225
+
226
+ ```ruby
227
+ # List all Flows
228
+ flows = client.flows.list(business_account_id: 'business_id')
229
+
230
+ # Get Flow details
231
+ flow = client.flows.get(flow_id: 'flow_id')
232
+
233
+ # Update Flow
234
+ client.flows.update(
235
+ flow_id: 'flow_id',
236
+ categories: ['APPOINTMENT_BOOKING', 'CUSTOMER_SUPPORT']
237
+ )
238
+
239
+ # Update Flow JSON
240
+ asset_response = client.flows.update_asset(
241
+ flow_id: 'flow_id',
242
+ asset: flow_json_hash
243
+ )
244
+
245
+ # Publish Flow
246
+ client.flows.publish(flow_id: 'flow_id')
247
+
248
+ # Get preview URL
249
+ preview = client.flows.preview(flow_id: 'flow_id')
250
+ puts preview.preview_url
251
+
252
+ # Deprecate Flow
253
+ client.flows.deprecate(flow_id: 'flow_id')
254
+
255
+ # Delete Flow
256
+ client.flows.delete(flow_id: 'flow_id')
257
+ ```
258
+
259
+ ### Messages
260
+
261
+ Send various types of messages with the Messages resource:
262
+
263
+ #### Text Messages
264
+
265
+ ```ruby
266
+ # Simple text message
267
+ client.messages.send_text(
268
+ phone_number_id: 'phone_id',
269
+ to: '+1234567890',
270
+ body: 'Hello World!'
271
+ )
272
+
273
+ # Text with URL preview
274
+ client.messages.send_text(
275
+ phone_number_id: 'phone_id',
276
+ to: '+1234567890',
277
+ body: 'Check this out: https://example.com',
278
+ preview_url: true
279
+ )
280
+ ```
281
+
282
+ #### Media Messages
283
+
284
+ ```ruby
285
+ # Send image
286
+ client.messages.send_image(
287
+ phone_number_id: 'phone_id',
288
+ to: '+1234567890',
289
+ image: {
290
+ link: 'https://example.com/image.jpg',
291
+ caption: 'Beautiful sunset'
292
+ }
293
+ )
294
+
295
+ # Send document
296
+ client.messages.send_document(
297
+ phone_number_id: 'phone_id',
298
+ to: '+1234567890',
299
+ document: {
300
+ id: 'media_id', # or link: 'https://...'
301
+ filename: 'report.pdf',
302
+ caption: 'Monthly report'
303
+ }
304
+ )
305
+
306
+ # Send audio
307
+ client.messages.send_audio(
308
+ phone_number_id: 'phone_id',
309
+ to: '+1234567890',
310
+ audio: { id: 'audio_media_id' }
311
+ )
312
+
313
+ # Send voice note (OGG/OPUS format recommended)
314
+ client.messages.send_audio(
315
+ phone_number_id: 'phone_id',
316
+ to: '+1234567890',
317
+ audio: { link: 'https://example.com/voice-note.ogg' },
318
+ voice: true # Marks as voice note
319
+ )
320
+
321
+ # Send video
322
+ client.messages.send_video(
323
+ phone_number_id: 'phone_id',
324
+ to: '+1234567890',
325
+ video: {
326
+ link: 'https://example.com/video.mp4',
327
+ caption: 'Tutorial video'
328
+ }
329
+ )
330
+ ```
331
+
332
+ #### Group Messaging
333
+
334
+ Send messages to WhatsApp groups by setting `recipient_type: 'group'`:
335
+
336
+ ```ruby
337
+ # Send text to group
338
+ client.messages.send_text(
339
+ phone_number_id: 'phone_id',
340
+ to: '120363XXXXXXXXX@g.us', # Group ID format
341
+ body: 'Hello everyone!',
342
+ recipient_type: 'group'
343
+ )
344
+
345
+ # Send image to group
346
+ client.messages.send_image(
347
+ phone_number_id: 'phone_id',
348
+ to: '120363XXXXXXXXX@g.us',
349
+ image: { link: 'https://example.com/team-photo.jpg' },
350
+ caption: 'Team photo from our event',
351
+ recipient_type: 'group'
352
+ )
353
+ ```
354
+
355
+ **Note:** Group messaging works with all message types (text, images, videos, documents, interactive messages, etc.)
356
+
357
+ **Group ID Format:** `XXXXXXXXX@g.us` (WhatsApp group identifier)
358
+
359
+ #### Location Messages
360
+
361
+ ```ruby
362
+ # Send location
363
+ client.messages.send_location(
364
+ phone_number_id: 'phone_id',
365
+ to: '+1234567890',
366
+ latitude: 37.7749,
367
+ longitude: -122.4194,
368
+ name: 'Our Office',
369
+ address: '123 Main St, San Francisco, CA'
370
+ )
371
+
372
+ # Request user's location
373
+ client.messages.send_interactive_location_request(
374
+ phone_number_id: 'phone_id',
375
+ to: '+1234567890',
376
+ body_text: 'Please share your location for delivery',
377
+ footer_text: 'Your privacy is important to us'
378
+ )
379
+
380
+ # Request location with header
381
+ client.messages.send_interactive_location_request(
382
+ phone_number_id: 'phone_id',
383
+ to: '+1234567890',
384
+ header: {
385
+ type: 'image',
386
+ image: { link: 'https://example.com/map-icon.png' }
387
+ },
388
+ body_text: 'Share your location to find the nearest store',
389
+ footer_text: 'Tap to share'
390
+ )
391
+ ```
392
+
393
+ #### Interactive Messages
394
+
395
+ ```ruby
396
+ # Button interactive message
397
+ client.messages.send_interactive_buttons(
398
+ phone_number_id: 'phone_id',
399
+ to: '+1234567890',
400
+ body_text: 'Choose an option:',
401
+ buttons: [
402
+ {
403
+ type: 'reply',
404
+ reply: {
405
+ id: 'option_1',
406
+ title: 'Option 1'
407
+ }
408
+ },
409
+ {
410
+ type: 'reply',
411
+ reply: {
412
+ id: 'option_2',
413
+ title: 'Option 2'
414
+ }
415
+ }
416
+ ],
417
+ header: {
418
+ type: 'text',
419
+ text: 'Menu'
420
+ }
421
+ )
422
+
423
+ # List interactive message
424
+ client.messages.send_interactive_list(
425
+ phone_number_id: 'phone_id',
426
+ to: '+1234567890',
427
+ body_text: 'Please select from the list:',
428
+ button_text: 'View Options',
429
+ sections: [
430
+ {
431
+ title: 'Section 1',
432
+ rows: [
433
+ {
434
+ id: 'item_1',
435
+ title: 'Item 1',
436
+ description: 'Description 1'
437
+ }
438
+ ]
439
+ }
440
+ ]
441
+ )
442
+ ```
443
+
444
+ #### Interactive CTA URL Messages
445
+
446
+ Send messages with Call-to-Action buttons that open URLs:
447
+
448
+ ```ruby
449
+ # With image header
450
+ client.messages.send_interactive_cta_url(
451
+ phone_number_id: 'phone_id',
452
+ to: '+1234567890',
453
+ header: {
454
+ type: 'image',
455
+ image: { link: 'https://example.com/banner.jpg' }
456
+ },
457
+ body_text: 'Get 25% off your first purchase!',
458
+ display_text: 'Shop Now', # Max 20 characters
459
+ url: 'https://shop.example.com?utm_source=whatsapp',
460
+ footer_text: 'Limited time offer'
461
+ )
462
+
463
+ # With text header
464
+ client.messages.send_interactive_cta_url(
465
+ phone_number_id: 'phone_id',
466
+ to: '+1234567890',
467
+ header: {
468
+ type: 'text',
469
+ text: 'Special Offer'
470
+ },
471
+ body_text: 'Join our exclusive members club!',
472
+ display_text: 'Join Now',
473
+ url: 'https://members.example.com/signup'
474
+ )
475
+
476
+ # With video or document header
477
+ client.messages.send_interactive_cta_url(
478
+ phone_number_id: 'phone_id',
479
+ to: '+1234567890',
480
+ header: {
481
+ type: 'video',
482
+ video: { link: 'https://example.com/demo.mp4' }
483
+ },
484
+ body_text: 'Watch our product in action!',
485
+ display_text: 'Learn More',
486
+ url: 'https://example.com/product'
487
+ )
488
+ ```
489
+
490
+ **Validations:**
491
+ - `body_text`: Max 1024 characters
492
+ - `display_text`: Max 20 characters
493
+ - `url`: Must be valid HTTP/HTTPS URL
494
+ - `footer_text`: Max 60 characters (optional)
495
+ - `header`: Supports text, image, video, or document
496
+
497
+ #### Catalog Messages
498
+
499
+ Send your product catalog directly in WhatsApp:
500
+
501
+ ```ruby
502
+ client.messages.send_interactive_catalog_message(
503
+ phone_number_id: 'phone_id',
504
+ to: '+1234567890',
505
+ body_text: 'Browse our entire product catalog!',
506
+ thumbnail_product_retailer_id: 'SKU-001', # Product SKU for thumbnail
507
+ footer_text: 'Tap to explore'
508
+ )
509
+ ```
510
+
511
+ **Parameters:**
512
+ - `body_text`: Max 1024 characters
513
+ - `thumbnail_product_retailer_id`: Product SKU to display as thumbnail (required)
514
+ - `footer_text`: Max 60 characters (optional)
515
+
516
+ #### Template Messages
517
+
518
+ ```ruby
519
+ # Simple template
520
+ client.messages.send_template(
521
+ phone_number_id: 'phone_id',
522
+ to: '+1234567890',
523
+ name: 'hello_world',
524
+ language: 'en_US'
525
+ )
526
+
527
+ # Template with parameters
528
+ client.messages.send_template(
529
+ phone_number_id: 'phone_id',
530
+ to: '+1234567890',
531
+ name: 'appointment_reminder',
532
+ language: 'en_US',
533
+ components: [
534
+ {
535
+ type: 'body',
536
+ parameters: [
537
+ { type: 'text', text: 'John Doe' },
538
+ { type: 'text', text: 'Tomorrow at 2 PM' }
539
+ ]
540
+ }
541
+ ]
542
+ )
543
+ ```
544
+
545
+ #### Message Reactions
546
+
547
+ ```ruby
548
+ # Add reaction
549
+ client.messages.send_reaction(
550
+ phone_number_id: 'phone_id',
551
+ to: '+1234567890',
552
+ message_id: 'message_to_react_to',
553
+ emoji: '👍'
554
+ )
555
+
556
+ # Remove reaction
557
+ client.messages.send_reaction(
558
+ phone_number_id: 'phone_id',
559
+ to: '+1234567890',
560
+ message_id: 'message_to_react_to',
561
+ emoji: nil
562
+ )
563
+ ```
564
+
565
+ #### Message Status
566
+
567
+ ```ruby
568
+ # Mark message as read
569
+ client.messages.mark_read(
570
+ phone_number_id: 'phone_id',
571
+ message_id: 'message_id'
572
+ )
573
+
574
+ # Send typing indicator
575
+ client.messages.send_typing_indicator(
576
+ phone_number_id: 'phone_id',
577
+ to: '+1234567890'
578
+ )
579
+ ```
580
+
581
+ ### Media Management
582
+
583
+ Handle media uploads, downloads, and management:
584
+
585
+ ```ruby
586
+ # Upload media
587
+ upload_response = client.media.upload(
588
+ phone_number_id: 'phone_id',
589
+ type: 'image',
590
+ file: '/path/to/image.jpg'
591
+ )
592
+
593
+ media_id = upload_response.id
594
+
595
+ # Get media metadata
596
+ metadata = client.media.get(media_id: media_id)
597
+ puts "File size: #{metadata.file_size} bytes"
598
+ puts "MIME type: #{metadata.mime_type}"
599
+
600
+ # Download media
601
+ content = client.media.download(
602
+ media_id: media_id,
603
+ as: :binary
604
+ )
605
+
606
+ # Save media to file
607
+ client.media.save_to_file(
608
+ media_id: media_id,
609
+ filepath: '/path/to/save/file.jpg'
610
+ )
611
+
612
+ # Delete media
613
+ client.media.delete(media_id: media_id)
614
+ ```
615
+
616
+ ### Template Management
617
+
618
+ Create, manage, and use message templates:
619
+
620
+ ```ruby
621
+ # List templates
622
+ templates = client.templates.list(
623
+ business_account_id: 'your_business_id',
624
+ status: 'APPROVED'
625
+ )
626
+
627
+ # Create marketing template
628
+ template_data = client.templates.build_marketing_template(
629
+ name: 'summer_sale',
630
+ language: 'en_US',
631
+ body: 'Hi {{1}}! Our summer sale is here with {{2}} off!',
632
+ header: {
633
+ type: 'HEADER',
634
+ format: 'TEXT',
635
+ text: 'Summer Sale 🌞'
636
+ },
637
+ footer: 'Limited time offer',
638
+ buttons: [
639
+ {
640
+ type: 'URL',
641
+ text: 'Shop Now',
642
+ url: 'https://shop.example.com'
643
+ }
644
+ ],
645
+ body_example: {
646
+ body_text: [['John', '25%']]
647
+ }
648
+ )
649
+
650
+ response = client.templates.create(
651
+ business_account_id: 'your_business_id',
652
+ **template_data
653
+ )
654
+
655
+ # Create authentication template
656
+ auth_template = client.templates.build_authentication_template(
657
+ name: 'verify_code',
658
+ language: 'en_US',
659
+ ttl_seconds: 300
660
+ )
661
+
662
+ client.templates.create(
663
+ business_account_id: 'your_business_id',
664
+ **auth_template
665
+ )
666
+
667
+ # Delete template
668
+ client.templates.delete(
669
+ business_account_id: 'your_business_id',
670
+ name: 'old_template',
671
+ language: 'en_US'
672
+ )
673
+ ```
674
+
675
+ ### Advanced Features (Kapso Proxy)
676
+
677
+ Access enhanced features with Kapso proxy:
678
+
679
+ ```ruby
680
+ # Initialize Kapso client
681
+ kapso_client = KapsoClientRuby::Client.new(
682
+ kapso_api_key: 'your_kapso_key',
683
+ base_url: 'https://app.kapso.ai/api/meta'
684
+ )
685
+
686
+ # Message history
687
+ messages = kapso_client.messages.query(
688
+ phone_number_id: 'phone_id',
689
+ direction: 'inbound',
690
+ since: '2024-01-01T00:00:00Z',
691
+ limit: 50
692
+ )
693
+
694
+ # Conversation management
695
+ conversations = kapso_client.conversations.list(
696
+ phone_number_id: 'phone_id',
697
+ status: 'active'
698
+ )
699
+
700
+ conversation = kapso_client.conversations.get(
701
+ conversation_id: conversations.data.first.id
702
+ )
703
+
704
+ # Update conversation status
705
+ kapso_client.conversations.update_status(
706
+ conversation_id: conversation.id,
707
+ status: 'archived'
708
+ )
709
+
710
+ # Contact management
711
+ contacts = kapso_client.contacts.list(
712
+ phone_number_id: 'phone_id',
713
+ limit: 100
714
+ )
715
+
716
+ # Update contact metadata
717
+ kapso_client.contacts.update(
718
+ phone_number_id: 'phone_id',
719
+ wa_id: 'contact_wa_id',
720
+ metadata: {
721
+ tags: ['premium', 'customer'],
722
+ source: 'website'
723
+ }
724
+ )
725
+
726
+ # Search contacts
727
+ results = kapso_client.contacts.search(
728
+ phone_number_id: 'phone_id',
729
+ query: 'john',
730
+ search_in: ['profile_name', 'phone_number']
731
+ )
732
+ ```
733
+
734
+ ## Configuration
735
+
736
+ ### Global Configuration
737
+
738
+ ```ruby
739
+ KapsoClientRuby.configure do |config|
740
+ config.debug = true
741
+ config.timeout = 60
742
+ config.open_timeout = 10
743
+ config.max_retries = 3
744
+ config.retry_delay = 1.0
745
+ end
746
+ ```
747
+
748
+ ### Client Configuration
749
+
750
+ ```ruby
751
+ client = KapsoClientRuby::Client.new(
752
+ access_token: 'token',
753
+ debug: true,
754
+ timeout: 30,
755
+ logger: Logger.new('whatsapp.log')
756
+ )
757
+ ```
758
+
759
+ ### Debug Logging
760
+
761
+ Enable debug logging to see detailed HTTP requests and responses:
762
+
763
+ ```ruby
764
+ # Enable debug mode
765
+ client = KapsoClientRuby::Client.new(
766
+ access_token: 'token',
767
+ debug: true
768
+ )
769
+
770
+ # Custom logger
771
+ logger = Logger.new(STDOUT)
772
+ logger.level = Logger::DEBUG
773
+
774
+ client = KapsoClientRuby::Client.new(
775
+ access_token: 'token',
776
+ logger: logger
777
+ )
778
+ ```
779
+
780
+ ## Error Handling
781
+
782
+ The SDK provides comprehensive error handling with detailed categorization:
783
+
784
+ ```ruby
785
+ begin
786
+ client.messages.send_text(
787
+ phone_number_id: 'phone_id',
788
+ to: 'invalid_number',
789
+ body: 'Test'
790
+ )
791
+ rescue KapsoClientRuby::Errors::GraphApiError => e
792
+ puts "Error: #{e.message}"
793
+ puts "Category: #{e.category}"
794
+ puts "HTTP Status: #{e.http_status}"
795
+ puts "Code: #{e.code}"
796
+
797
+ # Check error type
798
+ case e.category
799
+ when :authorization
800
+ puts "Authentication failed - check your access token"
801
+ when :parameter
802
+ puts "Invalid parameter - check phone number format"
803
+ when :throttling
804
+ puts "Rate limited - wait before retrying"
805
+ if e.retry_hint[:retry_after_ms]
806
+ sleep(e.retry_hint[:retry_after_ms] / 1000.0)
807
+ end
808
+ when :template
809
+ puts "Template error - check template name and parameters"
810
+ when :media
811
+ puts "Media error - check file format and size"
812
+ end
813
+
814
+ # Check retry recommendations
815
+ case e.retry_hint[:action]
816
+ when :retry
817
+ puts "Safe to retry this request"
818
+ when :retry_after
819
+ puts "Retry after specified delay: #{e.retry_hint[:retry_after_ms]}ms"
820
+ when :do_not_retry
821
+ puts "Do not retry - permanent error"
822
+ when :fix_and_retry
823
+ puts "Fix the request and retry"
824
+ when :refresh_token
825
+ puts "Access token needs to be refreshed"
826
+ end
827
+ end
828
+ ```
829
+
830
+ ### Error Categories
831
+
832
+ - `:authorization` - Authentication and token errors
833
+ - `:permission` - Permission and access errors
834
+ - `:parameter` - Invalid parameters or format errors
835
+ - `:throttling` - Rate limiting errors
836
+ - `:template` - Template-related errors
837
+ - `:media` - Media upload/download errors
838
+ - `:phone_registration` - Phone number registration errors
839
+ - `:integrity` - Message integrity errors
840
+ - `:business_eligibility` - Business account eligibility errors
841
+ - `:reengagement_window` - 24-hour messaging window errors
842
+ - `:waba_config` - WhatsApp Business Account configuration errors
843
+ - `:flow` - WhatsApp Flow errors
844
+ - `:synchronization` - Data synchronization errors
845
+ - `:server` - Server-side errors
846
+ - `:unknown` - Unclassified errors
847
+
848
+ ### Automatic Retry Logic
849
+
850
+ ```ruby
851
+ def send_with_retry(client, max_retries = 3)
852
+ retries = 0
853
+
854
+ begin
855
+ client.messages.send_text(
856
+ phone_number_id: 'phone_id',
857
+ to: '+1234567890',
858
+ body: 'Test message'
859
+ )
860
+ rescue KapsoClientRuby::Errors::GraphApiError => e
861
+ retries += 1
862
+
863
+ case e.retry_hint[:action]
864
+ when :retry
865
+ if retries <= max_retries
866
+ sleep(retries * 2) # Exponential backoff
867
+ retry
868
+ end
869
+ when :retry_after
870
+ if e.retry_hint[:retry_after_ms] && retries <= max_retries
871
+ sleep(e.retry_hint[:retry_after_ms] / 1000.0)
872
+ retry
873
+ end
874
+ end
875
+
876
+ raise # Re-raise if no retry
877
+ end
878
+ end
879
+ ```
880
+
881
+ ## Webhook Handling
882
+
883
+ Handle incoming webhooks from WhatsApp:
884
+
885
+ ```ruby
886
+ # Verify webhook signature
887
+ def verify_webhook_signature(payload, signature, app_secret)
888
+ require 'openssl'
889
+
890
+ sig_hash = signature.sub('sha256=', '')
891
+ expected_sig = OpenSSL::HMAC.hexdigest('sha256', app_secret, payload)
892
+
893
+ sig_hash == expected_sig
894
+ end
895
+
896
+ # In your webhook endpoint
897
+ def handle_webhook(request)
898
+ payload = request.body.read
899
+ signature = request.headers['X-Hub-Signature-256']
900
+
901
+ unless verify_webhook_signature(payload, signature, ENV['WHATSAPP_APP_SECRET'])
902
+ return [401, {}, ['Unauthorized']]
903
+ end
904
+
905
+ webhook_data = JSON.parse(payload)
906
+
907
+ # Process webhook data
908
+ webhook_data['entry'].each do |entry|
909
+ entry['changes'].each do |change|
910
+ if change['field'] == 'messages'
911
+ messages = change['value']['messages'] || []
912
+ messages.each do |message|
913
+ handle_incoming_message(message)
914
+ end
915
+ end
916
+ end
917
+ end
918
+
919
+ [200, {}, ['OK']]
920
+ end
921
+
922
+ def handle_incoming_message(message)
923
+ case message['type']
924
+ when 'text'
925
+ puts "Received text: #{message['text']['body']}"
926
+ when 'image'
927
+ puts "Received image: #{message['image']['id']}"
928
+ when 'interactive'
929
+ puts "Received interactive response: #{message['interactive']}"
930
+ end
931
+ end
932
+ ```
933
+
934
+ ## Testing
935
+
936
+ Run the test suite:
937
+
938
+ ```bash
939
+ # Install development dependencies
940
+ bundle install
941
+
942
+ # Run tests
943
+ bundle exec rspec
944
+
945
+ # Run tests with coverage
946
+ bundle exec rspec --format documentation
947
+
948
+ # Run rubocop for style checking
949
+ bundle exec rubocop
950
+ ```
951
+
952
+ ### Testing with VCR
953
+
954
+ The SDK includes VCR cassettes for testing without making real API calls:
955
+
956
+ ```ruby
957
+ # spec/spec_helper.rb
958
+ require 'vcr'
959
+
960
+ VCR.configure do |config|
961
+ config.cassette_library_dir = 'spec/vcr_cassettes'
962
+ config.hook_into :webmock
963
+ config.configure_rspec_metadata!
964
+
965
+ # Filter sensitive data
966
+ config.filter_sensitive_data('<ACCESS_TOKEN>') { ENV['WHATSAPP_ACCESS_TOKEN'] }
967
+ config.filter_sensitive_data('<PHONE_NUMBER_ID>') { ENV['PHONE_NUMBER_ID'] }
968
+ end
969
+
970
+ # In your tests
971
+ RSpec.describe 'Messages' do
972
+ it 'sends text message', :vcr do
973
+ client = KapsoClientRuby::Client.new(access_token: 'test_token')
974
+
975
+ response = client.messages.send_text(
976
+ phone_number_id: 'test_phone_id',
977
+ to: '+1234567890',
978
+ body: 'Test message'
979
+ )
980
+
981
+ expect(response.messages.first.id).to be_present
982
+ end
983
+ end
984
+ ```
985
+
986
+ ## Examples
987
+
988
+ See the [examples](examples/) directory for comprehensive usage examples:
989
+
990
+ - [Basic Messaging](examples/basic_messaging.rb) - Text, media, and template messages
991
+ - [Media Management](examples/media_management.rb) - Upload, download, and manage media
992
+ - [Template Management](examples/template_management.rb) - Create and manage templates
993
+ - [Advanced Features](examples/advanced_features.rb) - Kapso proxy features and analytics
994
+
995
+ ## Requirements
996
+
997
+ - Ruby >= 2.7.0
998
+ - Faraday >= 2.0
999
+ - A WhatsApp Business Account with Cloud API access
1000
+ - Valid access token from Meta or Kapso API key
1001
+
1002
+ ## Contributing
1003
+
1004
+ Bug reports and pull requests are welcome on GitHub at https://github.com/gokapso/whatsapp-cloud-api-ruby.
1005
+
1006
+ ### Development Setup
1007
+
1008
+ ```bash
1009
+ git clone https://github.com/gokapso/whatsapp-cloud-api-ruby.git
1010
+ cd whatsapp-cloud-api-ruby
1011
+ bundle install
1012
+ ```
1013
+
1014
+ ### Running Tests
1015
+
1016
+ ```bash
1017
+ # Run all tests
1018
+ bundle exec rspec
1019
+
1020
+ # Run specific test file
1021
+ bundle exec rspec spec/client_spec.rb
1022
+
1023
+ # Run with coverage
1024
+ COVERAGE=true bundle exec rspec
1025
+ ```
1026
+
1027
+ ### Code Style
1028
+
1029
+ ```bash
1030
+ # Check style
1031
+ bundle exec rubocop
1032
+
1033
+ # Auto-fix issues
1034
+ bundle exec rubocop -A
1035
+ ```
1036
+
1037
+ ## License
1038
+
1039
+ This gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
1040
+
1041
+ ## Support
1042
+
1043
+ - 📖 [WhatsApp Cloud API Documentation](https://developers.facebook.com/docs/whatsapp/cloud-api/)
1044
+ - 🌐 [Kapso Platform](https://kapso.ai/) for enhanced features
1045
+ - 🐛 [Issue Tracker](https://github.com/PabloB07/kapso-client-ruby/issues)
1046
+ - 📧 Email: support@kapso.ai
1047
+
1048
+ ## Changelog
1049
+
1050
+ See [CHANGELOG.md](CHANGELOG.md) for version history and updates.
1051
+
1052
+ ---
1053
+
735
1054
  Built with ❤️ for the [Kapso](https://kapso.ai) team