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