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