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
@@ -1,350 +1,353 @@
1
- # frozen_string_literal: true
2
-
3
- require 'KapsoClientRuby'
4
-
5
- puts "=== Advanced Features with Kapso Proxy ==="
6
-
7
- # Initialize Kapso client
8
- kapso_client = KapsoClientRuby::Client.new(
9
- kapso_api_key: ENV['KAPSO_API_KEY'],
10
- base_url: 'https://app.kapso.ai/api/meta',
11
- debug: true
12
- )
13
-
14
- phone_number_id = ENV['PHONE_NUMBER_ID']
15
-
16
- # Example 1: Message History and Analytics
17
- puts "\n--- Message History ---"
18
-
19
- begin
20
- # Query message history
21
- messages = kapso_client.messages.query(
22
- phone_number_id: phone_number_id,
23
- direction: 'inbound',
24
- since: '2024-01-01T00:00:00Z',
25
- limit: 10
26
- )
27
-
28
- puts "Found #{messages.data.length} messages:"
29
- messages.data.each do |message|
30
- puts "- #{message['id']}: #{message['type']} from #{message['from']}"
31
- end
32
-
33
- # Get messages by conversation
34
- if messages.data.any? && messages.data.first['conversation_id']
35
- conv_messages = kapso_client.messages.list_by_conversation(
36
- phone_number_id: phone_number_id,
37
- conversation_id: messages.data.first['conversation_id'],
38
- limit: 5
39
- )
40
-
41
- puts "\nConversation messages: #{conv_messages.data.length}"
42
- end
43
-
44
- rescue KapsoClientRuby::Errors::KapsoProxyRequiredError => e
45
- puts "Kapso Proxy required: #{e.message}"
46
- rescue KapsoClientRuby::Errors::GraphApiError => e
47
- puts "Message history error: #{e.message}"
48
- end
49
-
50
- # Example 2: Conversation Management
51
- puts "\n--- Conversation Management ---"
52
-
53
- begin
54
- # List active conversations
55
- conversations = kapso_client.conversations.list(
56
- phone_number_id: phone_number_id,
57
- status: 'active',
58
- limit: 10
59
- )
60
-
61
- puts "Active conversations: #{conversations.data.length}"
62
-
63
- conversations.data.each do |conv|
64
- puts "Conversation #{conv.id}:"
65
- puts " Phone: #{conv.phone_number}"
66
- puts " Status: #{conv.status}"
67
- puts " Last Active: #{conv.last_active_at}"
68
-
69
- if conv.kapso
70
- puts " Contact Name: #{conv.kapso['contact_name']}"
71
- puts " Messages Count: #{conv.kapso['messages_count']}"
72
- puts " Last Message: #{conv.kapso['last_message_text']}"
73
- end
74
- end
75
-
76
- # Get specific conversation details
77
- if conversations.data.any?
78
- conversation_id = conversations.data.first.id
79
-
80
- conv_details = kapso_client.conversations.get(
81
- conversation_id: conversation_id
82
- )
83
-
84
- puts "\nDetailed conversation info:"
85
- puts "ID: #{conv_details.id}"
86
- puts "Status: #{conv_details.status}"
87
- puts "Metadata: #{conv_details.metadata}"
88
-
89
- # Update conversation status
90
- kapso_client.conversations.update_status(
91
- conversation_id: conversation_id,
92
- status: 'archived'
93
- )
94
-
95
- puts "Conversation archived successfully"
96
-
97
- # Unarchive it
98
- kapso_client.conversations.unarchive(conversation_id: conversation_id)
99
- puts "Conversation unarchived"
100
- end
101
-
102
- rescue KapsoClientRuby::Errors::GraphApiError => e
103
- puts "Conversation management error: #{e.message}"
104
- end
105
-
106
- # Example 3: Contact Management
107
- puts "\n--- Contact Management ---"
108
-
109
- begin
110
- # List contacts
111
- contacts = kapso_client.contacts.list(
112
- phone_number_id: phone_number_id,
113
- limit: 10
114
- )
115
-
116
- puts "Found #{contacts.data.length} contacts:"
117
-
118
- contacts.data.each do |contact|
119
- puts "Contact #{contact.wa_id}:"
120
- puts " Phone: #{contact.phone_number}"
121
- puts " Profile Name: #{contact.profile_name}"
122
- puts " Metadata: #{contact.metadata}"
123
- end
124
-
125
- # Get specific contact
126
- if contacts.data.any?
127
- wa_id = contacts.data.first.wa_id
128
-
129
- contact_details = kapso_client.contacts.get(
130
- phone_number_id: phone_number_id,
131
- wa_id: wa_id
132
- )
133
-
134
- puts "\nContact details for #{wa_id}:"
135
- puts "Profile Name: #{contact_details.profile_name}"
136
-
137
- # Update contact metadata
138
- kapso_client.contacts.update(
139
- phone_number_id: phone_number_id,
140
- wa_id: wa_id,
141
- metadata: {
142
- tags: ['ruby_sdk', 'test_contact'],
143
- source: 'api_example',
144
- notes: 'Updated via Ruby SDK'
145
- }
146
- )
147
-
148
- puts "Contact metadata updated"
149
-
150
- # Add tags
151
- kapso_client.contacts.add_tags(
152
- phone_number_id: phone_number_id,
153
- wa_id: wa_id,
154
- tags: ['premium_customer']
155
- )
156
-
157
- puts "Tags added to contact"
158
-
159
- # Search contacts
160
- search_results = kapso_client.contacts.search(
161
- phone_number_id: phone_number_id,
162
- query: 'john',
163
- search_in: ['profile_name', 'phone_number']
164
- )
165
-
166
- puts "Search results: #{search_results.data.length} contacts"
167
- end
168
-
169
- rescue KapsoClientRuby::Errors::GraphApiError => e
170
- puts "Contact management error: #{e.message}"
171
- end
172
-
173
- # Example 4: Call Management
174
- puts "\n--- Call Management ---"
175
-
176
- begin
177
- # List recent calls
178
- calls = kapso_client.calls.list(
179
- phone_number_id: phone_number_id,
180
- direction: 'INBOUND',
181
- limit: 5
182
- )
183
-
184
- puts "Recent calls: #{calls.data.length}"
185
-
186
- calls.data.each do |call|
187
- puts "Call #{call.id}:"
188
- puts " Direction: #{call.direction}"
189
- puts " Status: #{call.status}"
190
- puts " Duration: #{call.duration_seconds} seconds"
191
- puts " Started: #{call.started_at}"
192
- end
193
-
194
- # Initiate a call (example - requires proper setup)
195
- begin
196
- call_response = kapso_client.calls.connect(
197
- phone_number_id: phone_number_id,
198
- to: '+1234567890',
199
- session: {
200
- sdp_type: 'offer',
201
- sdp: 'v=0\r\no=- 123456789 123456789 IN IP4 127.0.0.1\r\n...'
202
- }
203
- )
204
-
205
- puts "Call initiated: #{call_response.calls.first['id']}"
206
- rescue KapsoClientRuby::Errors::GraphApiError => e
207
- puts "Call initiation error (expected in example): #{e.message}"
208
- end
209
-
210
- rescue KapsoClientRuby::Errors::GraphApiError => e
211
- puts "Call management error: #{e.message}"
212
- end
213
-
214
- # Example 5: Advanced Error Handling and Monitoring
215
- puts "\n--- Advanced Error Handling ---"
216
-
217
- class WhatsAppMonitor
218
- def initialize(client)
219
- @client = client
220
- @error_counts = Hash.new(0)
221
- @last_errors = []
222
- end
223
-
224
- def send_with_monitoring(method, *args, **kwargs)
225
- start_time = Time.now
226
-
227
- begin
228
- result = @client.messages.public_send(method, **kwargs)
229
-
230
- duration = Time.now - start_time
231
- puts "✓ #{method} succeeded in #{duration.round(2)}s"
232
-
233
- result
234
- rescue KapsoClientRuby::Errors::GraphApiError => e
235
- duration = Time.now - start_time
236
- @error_counts[e.category] += 1
237
- @last_errors << {
238
- timestamp: Time.now,
239
- method: method,
240
- error: e,
241
- duration: duration
242
- }
243
-
244
- puts "✗ #{method} failed in #{duration.round(2)}s"
245
- puts " Category: #{e.category}"
246
- puts " Message: #{e.message}"
247
- puts " Retry: #{e.retry_hint[:action]}"
248
-
249
- # Automatic retry logic
250
- case e.retry_hint[:action]
251
- when :retry
252
- if kwargs[:_retry_count].to_i < 3
253
- retry_count = kwargs[:_retry_count].to_i + 1
254
- puts " Auto-retrying (#{retry_count}/3)..."
255
- sleep(retry_count)
256
- return send_with_monitoring(method, **kwargs.merge(_retry_count: retry_count))
257
- end
258
- when :retry_after
259
- if e.retry_hint[:retry_after_ms] && e.retry_hint[:retry_after_ms] < 30000
260
- delay = e.retry_hint[:retry_after_ms] / 1000.0
261
- puts " Waiting #{delay}s for rate limit..."
262
- sleep(delay)
263
- return send_with_monitoring(method, **kwargs)
264
- end
265
- end
266
-
267
- raise
268
- end
269
- end
270
-
271
- def print_statistics
272
- puts "\n--- Error Statistics ---"
273
- puts "Total error categories: #{@error_counts.keys.length}"
274
- @error_counts.each do |category, count|
275
- puts " #{category}: #{count} errors"
276
- end
277
-
278
- if @last_errors.any?
279
- puts "\nRecent errors:"
280
- @last_errors.last(3).each do |error_info|
281
- puts " #{error_info[:timestamp]}: #{error_info[:method]} -> #{error_info[:error].category}"
282
- end
283
- end
284
- end
285
- end
286
-
287
- # Test the monitoring system
288
- monitor = WhatsAppMonitor.new(kapso_client)
289
-
290
- # Test various operations with monitoring
291
- test_operations = [
292
- [:send_text, {
293
- phone_number_id: phone_number_id,
294
- to: '+1234567890',
295
- body: 'Test message from monitoring system'
296
- }],
297
- [:send_template, {
298
- phone_number_id: phone_number_id,
299
- to: '+1234567890',
300
- name: 'nonexistent_template',
301
- language: 'en_US'
302
- }],
303
- [:send_image, {
304
- phone_number_id: phone_number_id,
305
- to: '+1234567890',
306
- image: { link: 'https://invalid-url.example/image.jpg' }
307
- }]
308
- ]
309
-
310
- test_operations.each do |method, kwargs|
311
- begin
312
- monitor.send_with_monitoring(method, **kwargs)
313
- rescue => e
314
- puts "Final error for #{method}: #{e.message}"
315
- end
316
-
317
- sleep(1) # Rate limiting prevention
318
- end
319
-
320
- monitor.print_statistics
321
-
322
- # Example 6: Webhook Signature Verification (helper function)
323
- puts "\n--- Webhook Signature Verification ---"
324
-
325
- def verify_webhook_signature(payload, signature, app_secret)
326
- require 'openssl'
327
-
328
- # Extract signature from header (format: "sha256=...")
329
- sig_hash = signature.sub('sha256=', '')
330
-
331
- # Calculate expected signature
332
- expected_sig = OpenSSL::HMAC.hexdigest('sha256', app_secret, payload)
333
-
334
- # Secure comparison
335
- sig_hash == expected_sig
336
- end
337
-
338
- # Example webhook payload verification
339
- webhook_payload = '{"object":"whatsapp_business_account","entry":[...]}'
340
- webhook_signature = 'sha256=abcdef123456...' # From X-Hub-Signature-256 header
341
- app_secret = ENV['WHATSAPP_APP_SECRET']
342
-
343
- if app_secret
344
- is_valid = verify_webhook_signature(webhook_payload, webhook_signature, app_secret)
345
- puts "Webhook signature valid: #{is_valid}"
346
- else
347
- puts "Set WHATSAPP_APP_SECRET to test webhook verification"
348
- end
349
-
1
+ # frozen_string_literal: true
2
+
3
+ require 'kapso-client-ruby'
4
+ require 'dotenv'
5
+
6
+ Dotenv.load
7
+
8
+ puts "=== Advanced Features with Kapso Proxy ==="
9
+
10
+ # Initialize Kapso client
11
+ kapso_client = KapsoClientRuby::Client.new(
12
+ kapso_api_key: ENV['KAPSO_API_KEY'],
13
+ base_url: 'https://app.kapso.ai/api/meta',
14
+ debug: true
15
+ )
16
+
17
+ phone_number_id = ENV['PHONE_NUMBER_ID']
18
+
19
+ # Example 1: Message History and Analytics
20
+ puts "\n--- Message History ---"
21
+
22
+ begin
23
+ # Query message history
24
+ messages = kapso_client.messages.query(
25
+ phone_number_id: phone_number_id,
26
+ direction: 'inbound',
27
+ since: '2024-01-01T00:00:00Z',
28
+ limit: 10
29
+ )
30
+
31
+ puts "Found #{messages.data.length} messages:"
32
+ messages.data.each do |message|
33
+ puts "- #{message['id']}: #{message['type']} from #{message['from']}"
34
+ end
35
+
36
+ # Get messages by conversation
37
+ if messages.data.any? && messages.data.first['conversation_id']
38
+ conv_messages = kapso_client.messages.list_by_conversation(
39
+ phone_number_id: phone_number_id,
40
+ conversation_id: messages.data.first['conversation_id'],
41
+ limit: 5
42
+ )
43
+
44
+ puts "\nConversation messages: #{conv_messages.data.length}"
45
+ end
46
+
47
+ rescue KapsoClientRuby::Errors::KapsoProxyRequiredError => e
48
+ puts "Kapso Proxy required: #{e.message}"
49
+ rescue KapsoClientRuby::Errors::GraphApiError => e
50
+ puts "Message history error: #{e.message}"
51
+ end
52
+
53
+ # Example 2: Conversation Management
54
+ puts "\n--- Conversation Management ---"
55
+
56
+ begin
57
+ # List active conversations
58
+ conversations = kapso_client.conversations.list(
59
+ phone_number_id: phone_number_id,
60
+ status: 'active',
61
+ limit: 10
62
+ )
63
+
64
+ puts "Active conversations: #{conversations.data.length}"
65
+
66
+ conversations.data.each do |conv|
67
+ puts "Conversation #{conv.id}:"
68
+ puts " Phone: #{conv.phone_number}"
69
+ puts " Status: #{conv.status}"
70
+ puts " Last Active: #{conv.last_active_at}"
71
+
72
+ if conv.kapso
73
+ puts " Contact Name: #{conv.kapso['contact_name']}"
74
+ puts " Messages Count: #{conv.kapso['messages_count']}"
75
+ puts " Last Message: #{conv.kapso['last_message_text']}"
76
+ end
77
+ end
78
+
79
+ # Get specific conversation details
80
+ if conversations.data.any?
81
+ conversation_id = conversations.data.first.id
82
+
83
+ conv_details = kapso_client.conversations.get(
84
+ conversation_id: conversation_id
85
+ )
86
+
87
+ puts "\nDetailed conversation info:"
88
+ puts "ID: #{conv_details.id}"
89
+ puts "Status: #{conv_details.status}"
90
+ puts "Metadata: #{conv_details.metadata}"
91
+
92
+ # Update conversation status
93
+ kapso_client.conversations.update_status(
94
+ conversation_id: conversation_id,
95
+ status: 'archived'
96
+ )
97
+
98
+ puts "Conversation archived successfully"
99
+
100
+ # Unarchive it
101
+ kapso_client.conversations.unarchive(conversation_id: conversation_id)
102
+ puts "Conversation unarchived"
103
+ end
104
+
105
+ rescue KapsoClientRuby::Errors::GraphApiError => e
106
+ puts "Conversation management error: #{e.message}"
107
+ end
108
+
109
+ # Example 3: Contact Management
110
+ puts "\n--- Contact Management ---"
111
+
112
+ begin
113
+ # List contacts
114
+ contacts = kapso_client.contacts.list(
115
+ phone_number_id: phone_number_id,
116
+ limit: 10
117
+ )
118
+
119
+ puts "Found #{contacts.data.length} contacts:"
120
+
121
+ contacts.data.each do |contact|
122
+ puts "Contact #{contact.wa_id}:"
123
+ puts " Phone: #{contact.phone_number}"
124
+ puts " Profile Name: #{contact.profile_name}"
125
+ puts " Metadata: #{contact.metadata}"
126
+ end
127
+
128
+ # Get specific contact
129
+ if contacts.data.any?
130
+ wa_id = contacts.data.first.wa_id
131
+
132
+ contact_details = kapso_client.contacts.get(
133
+ phone_number_id: phone_number_id,
134
+ wa_id: wa_id
135
+ )
136
+
137
+ puts "\nContact details for #{wa_id}:"
138
+ puts "Profile Name: #{contact_details.profile_name}"
139
+
140
+ # Update contact metadata
141
+ kapso_client.contacts.update(
142
+ phone_number_id: phone_number_id,
143
+ wa_id: wa_id,
144
+ metadata: {
145
+ tags: ['ruby_sdk', 'test_contact'],
146
+ source: 'api_example',
147
+ notes: 'Updated via Ruby SDK'
148
+ }
149
+ )
150
+
151
+ puts "Contact metadata updated"
152
+
153
+ # Add tags
154
+ kapso_client.contacts.add_tags(
155
+ phone_number_id: phone_number_id,
156
+ wa_id: wa_id,
157
+ tags: ['premium_customer']
158
+ )
159
+
160
+ puts "Tags added to contact"
161
+
162
+ # Search contacts
163
+ search_results = kapso_client.contacts.search(
164
+ phone_number_id: phone_number_id,
165
+ query: 'john',
166
+ search_in: ['profile_name', 'phone_number']
167
+ )
168
+
169
+ puts "Search results: #{search_results.data.length} contacts"
170
+ end
171
+
172
+ rescue KapsoClientRuby::Errors::GraphApiError => e
173
+ puts "Contact management error: #{e.message}"
174
+ end
175
+
176
+ # Example 4: Call Management
177
+ puts "\n--- Call Management ---"
178
+
179
+ begin
180
+ # List recent calls
181
+ calls = kapso_client.calls.list(
182
+ phone_number_id: phone_number_id,
183
+ direction: 'INBOUND',
184
+ limit: 5
185
+ )
186
+
187
+ puts "Recent calls: #{calls.data.length}"
188
+
189
+ calls.data.each do |call|
190
+ puts "Call #{call.id}:"
191
+ puts " Direction: #{call.direction}"
192
+ puts " Status: #{call.status}"
193
+ puts " Duration: #{call.duration_seconds} seconds"
194
+ puts " Started: #{call.started_at}"
195
+ end
196
+
197
+ # Initiate a call (example - requires proper setup)
198
+ begin
199
+ call_response = kapso_client.calls.connect(
200
+ phone_number_id: phone_number_id,
201
+ to: '+1234567890',
202
+ session: {
203
+ sdp_type: 'offer',
204
+ sdp: 'v=0\r\no=- 123456789 123456789 IN IP4 127.0.0.1\r\n...'
205
+ }
206
+ )
207
+
208
+ puts "Call initiated: #{call_response.calls.first['id']}"
209
+ rescue KapsoClientRuby::Errors::GraphApiError => e
210
+ puts "Call initiation error (expected in example): #{e.message}"
211
+ end
212
+
213
+ rescue KapsoClientRuby::Errors::GraphApiError => e
214
+ puts "Call management error: #{e.message}"
215
+ end
216
+
217
+ # Example 5: Advanced Error Handling and Monitoring
218
+ puts "\n--- Advanced Error Handling ---"
219
+
220
+ class WhatsAppMonitor
221
+ def initialize(client)
222
+ @client = client
223
+ @error_counts = Hash.new(0)
224
+ @last_errors = []
225
+ end
226
+
227
+ def send_with_monitoring(method, *args, **kwargs)
228
+ start_time = Time.now
229
+
230
+ begin
231
+ result = @client.messages.public_send(method, **kwargs)
232
+
233
+ duration = Time.now - start_time
234
+ puts "✓ #{method} succeeded in #{duration.round(2)}s"
235
+
236
+ result
237
+ rescue KapsoClientRuby::Errors::GraphApiError => e
238
+ duration = Time.now - start_time
239
+ @error_counts[e.category] += 1
240
+ @last_errors << {
241
+ timestamp: Time.now,
242
+ method: method,
243
+ error: e,
244
+ duration: duration
245
+ }
246
+
247
+ puts " #{method} failed in #{duration.round(2)}s"
248
+ puts " Category: #{e.category}"
249
+ puts " Message: #{e.message}"
250
+ puts " Retry: #{e.retry_hint[:action]}"
251
+
252
+ # Automatic retry logic
253
+ case e.retry_hint[:action]
254
+ when :retry
255
+ if kwargs[:_retry_count].to_i < 3
256
+ retry_count = kwargs[:_retry_count].to_i + 1
257
+ puts " Auto-retrying (#{retry_count}/3)..."
258
+ sleep(retry_count)
259
+ return send_with_monitoring(method, **kwargs.merge(_retry_count: retry_count))
260
+ end
261
+ when :retry_after
262
+ if e.retry_hint[:retry_after_ms] && e.retry_hint[:retry_after_ms] < 30000
263
+ delay = e.retry_hint[:retry_after_ms] / 1000.0
264
+ puts " Waiting #{delay}s for rate limit..."
265
+ sleep(delay)
266
+ return send_with_monitoring(method, **kwargs)
267
+ end
268
+ end
269
+
270
+ raise
271
+ end
272
+ end
273
+
274
+ def print_statistics
275
+ puts "\n--- Error Statistics ---"
276
+ puts "Total error categories: #{@error_counts.keys.length}"
277
+ @error_counts.each do |category, count|
278
+ puts " #{category}: #{count} errors"
279
+ end
280
+
281
+ if @last_errors.any?
282
+ puts "\nRecent errors:"
283
+ @last_errors.last(3).each do |error_info|
284
+ puts " #{error_info[:timestamp]}: #{error_info[:method]} -> #{error_info[:error].category}"
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+ # Test the monitoring system
291
+ monitor = WhatsAppMonitor.new(kapso_client)
292
+
293
+ # Test various operations with monitoring
294
+ test_operations = [
295
+ [:send_text, {
296
+ phone_number_id: phone_number_id,
297
+ to: '+1234567890',
298
+ body: 'Test message from monitoring system'
299
+ }],
300
+ [:send_template, {
301
+ phone_number_id: phone_number_id,
302
+ to: '+1234567890',
303
+ name: 'nonexistent_template',
304
+ language: 'en_US'
305
+ }],
306
+ [:send_image, {
307
+ phone_number_id: phone_number_id,
308
+ to: '+1234567890',
309
+ image: { link: 'https://invalid-url.example/image.jpg' }
310
+ }]
311
+ ]
312
+
313
+ test_operations.each do |method, kwargs|
314
+ begin
315
+ monitor.send_with_monitoring(method, **kwargs)
316
+ rescue => e
317
+ puts "Final error for #{method}: #{e.message}"
318
+ end
319
+
320
+ sleep(1) # Rate limiting prevention
321
+ end
322
+
323
+ monitor.print_statistics
324
+
325
+ # Example 6: Webhook Signature Verification (helper function)
326
+ puts "\n--- Webhook Signature Verification ---"
327
+
328
+ def verify_webhook_signature(payload, signature, app_secret)
329
+ require 'openssl'
330
+
331
+ # Extract signature from header (format: "sha256=...")
332
+ sig_hash = signature.sub('sha256=', '')
333
+
334
+ # Calculate expected signature
335
+ expected_sig = OpenSSL::HMAC.hexdigest('sha256', app_secret, payload)
336
+
337
+ # Secure comparison
338
+ sig_hash == expected_sig
339
+ end
340
+
341
+ # Example webhook payload verification
342
+ webhook_payload = '{"object":"whatsapp_business_account","entry":[...]}'
343
+ webhook_signature = 'sha256=abcdef123456...' # From X-Hub-Signature-256 header
344
+ app_secret = ENV['WHATSAPP_APP_SECRET']
345
+
346
+ if app_secret
347
+ is_valid = verify_webhook_signature(webhook_payload, webhook_signature, app_secret)
348
+ puts "Webhook signature valid: #{is_valid}"
349
+ else
350
+ puts "Set WHATSAPP_APP_SECRET to test webhook verification"
351
+ end
352
+
350
353
  puts "\n=== Advanced Features Examples Completed ==="