flow_chat 0.8.1 → 0.9.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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.cliff.toml +74 -0
  3. data/.github/workflows/ci.yml +2 -3
  4. data/.github/workflows/release.yml +56 -0
  5. data/.standard.yml +4 -0
  6. data/CHANGELOG.md +22 -0
  7. data/CLAUDE.md +327 -0
  8. data/CONTRIBUTING.md +134 -0
  9. data/Gemfile +1 -0
  10. data/README.md +290 -105
  11. data/Rakefile +5 -1
  12. data/SECURITY.md +42 -349
  13. data/docs/architecture.md +510 -0
  14. data/docs/async-background-processing.md +298 -0
  15. data/docs/configuration.md +556 -226
  16. data/docs/factory-pattern.md +355 -0
  17. data/docs/gateway-context-variables.md +171 -0
  18. data/docs/gateway-development.md +723 -0
  19. data/docs/getting-started.md +429 -0
  20. data/docs/instrumentation.md +264 -153
  21. data/docs/platforms/telegram.md +1013 -0
  22. data/docs/platforms/ussd.md +693 -0
  23. data/docs/platforms/whatsapp.md +1395 -0
  24. data/docs/testing.md +243 -365
  25. data/examples/custom_session_id_example.rb +119 -0
  26. data/examples/http_controller.rb +154 -0
  27. data/examples/intercom_configuration_example.rb +118 -0
  28. data/examples/intercom_controller.rb +194 -0
  29. data/examples/multi_tenant_whatsapp_controller.rb +4 -4
  30. data/examples/simulator_controller.rb +21 -1
  31. data/examples/ussd_controller.rb +10 -10
  32. data/examples/whatsapp_controller.rb +2 -2
  33. data/flow_chat.gemspec +4 -0
  34. data/lib/flow_chat/{base_app.rb → app.rb} +28 -9
  35. data/lib/flow_chat/async_job.rb +176 -0
  36. data/lib/flow_chat/config.rb +23 -28
  37. data/lib/flow_chat/{base_executor.rb → executor.rb} +7 -12
  38. data/lib/flow_chat/factory.rb +94 -0
  39. data/lib/flow_chat/gateway_async_support.rb +106 -0
  40. data/lib/flow_chat/generic_async_job.rb +30 -0
  41. data/lib/flow_chat/http/gateway/simple.rb +125 -0
  42. data/lib/flow_chat/http/renderer.rb +41 -0
  43. data/lib/flow_chat/instrumentation/setup.rb +1 -1
  44. data/lib/flow_chat/instrumentation.rb +25 -0
  45. data/lib/flow_chat/intercom/client.rb +155 -0
  46. data/lib/flow_chat/intercom/configuration.rb +149 -0
  47. data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
  48. data/lib/flow_chat/intercom/renderer.rb +71 -0
  49. data/lib/flow_chat/phone_number_util.rb +49 -0
  50. data/lib/flow_chat/processor.rb +188 -0
  51. data/lib/flow_chat/renderers/markdown_support.rb +58 -0
  52. data/lib/flow_chat/session/cache_session_store.rb +1 -17
  53. data/lib/flow_chat/session/middleware.rb +43 -26
  54. data/lib/flow_chat/simulator/controller.rb +17 -5
  55. data/lib/flow_chat/simulator/views/simulator.html.erb +220 -8
  56. data/lib/flow_chat/telegram/client.rb +240 -0
  57. data/lib/flow_chat/telegram/configuration.rb +118 -0
  58. data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
  59. data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
  60. data/lib/flow_chat/telegram/renderer.rb +125 -0
  61. data/lib/flow_chat/telegram.rb +7 -0
  62. data/lib/flow_chat/ussd/gateway/nalo.rb +27 -11
  63. data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
  64. data/lib/flow_chat/ussd/renderer.rb +1 -1
  65. data/lib/flow_chat/version.rb +1 -1
  66. data/lib/flow_chat/whatsapp/client.rb +144 -13
  67. data/lib/flow_chat/whatsapp/configuration.rb +1 -1
  68. data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +144 -106
  69. data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
  70. data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
  71. data/lib/flow_chat/whatsapp/renderer.rb +145 -11
  72. data/lib/flow_chat.rb +11 -1
  73. data/lib/tasks/release.rake +165 -0
  74. metadata +88 -21
  75. data/docs/flows.md +0 -320
  76. data/docs/images/simulator.png +0 -0
  77. data/docs/media.md +0 -153
  78. data/docs/sessions.md +0 -433
  79. data/docs/ussd-setup.md +0 -322
  80. data/docs/whatsapp-setup.md +0 -162
  81. data/examples/whatsapp_message_job.rb +0 -113
  82. data/lib/flow_chat/base_processor.rb +0 -145
  83. data/lib/flow_chat/session/rails_session_store.rb +0 -68
  84. data/lib/flow_chat/ussd/app.rb +0 -6
  85. data/lib/flow_chat/ussd/gateway/nsano.rb +0 -98
  86. data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
  87. data/lib/flow_chat/ussd/processor.rb +0 -39
  88. data/lib/flow_chat/whatsapp/app.rb +0 -29
  89. data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
  90. data/lib/flow_chat/whatsapp/processor.rb +0 -32
  91. data/lib/flow_chat/whatsapp/send_job_support.rb +0 -79
@@ -0,0 +1,1395 @@
1
+ # WhatsApp Development Guide
2
+
3
+ FlowChat provides comprehensive WhatsApp Business API integration with support for rich media, interactive elements, secure webhook validation, and flexible deployment modes.
4
+
5
+ ## ✨ Key Features
6
+
7
+ - **Rich Media Support**: Images, documents, audio, video, and stickers
8
+ - **Interactive Elements**: Buttons (up to 3), lists (up to 10 items per section)
9
+ - **Secure Webhooks**: HMAC-SHA256 signature validation
10
+ - **Multiple Processing Modes**: Inline, background, and simulator
11
+ - **Media Upload & Download**: Direct file upload and media handling
12
+ - **Multi-Tenant Support**: Named configurations for different accounts
13
+ - **Development Tools**: Built-in simulator for testing flows
14
+
15
+ ## 🚀 Quick Start
16
+
17
+ FlowChat supports two WhatsApp gateway options:
18
+
19
+ ### Option 1: Meta Cloud API (Recommended)
20
+
21
+ ```ruby
22
+ # app/controllers/whatsapp_controller.rb
23
+ class WhatsappController < ApplicationController
24
+ skip_forgery_protection
25
+
26
+ def webhook
27
+ processor = FlowChat::Processor.new(self) do |config|
28
+ config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
29
+ config.use_session_store FlowChat::Session::CacheSessionStore
30
+ end
31
+
32
+ processor.run WelcomeFlow, :main_page
33
+ rescue => e
34
+ Rails.logger.error "WhatsApp webhook error: #{e.message}"
35
+ head :internal_server_error
36
+ end
37
+ end
38
+ ```
39
+
40
+ ### Option 2: Twilio WhatsApp API
41
+
42
+ ```ruby
43
+ # app/controllers/whatsapp_twilio_controller.rb
44
+ class WhatsappTwilioController < ApplicationController
45
+ skip_forgery_protection
46
+
47
+ def webhook
48
+ processor = FlowChat::Processor.new(self) do |config|
49
+ config.use_gateway FlowChat::Whatsapp::Gateway::Twilio
50
+ config.use_session_store FlowChat::Session::CacheSessionStore
51
+ end
52
+
53
+ processor.run WelcomeFlow, :main_page
54
+ rescue => e
55
+ Rails.logger.error "Twilio WhatsApp webhook error: #{e.message}"
56
+ head :internal_server_error
57
+ end
58
+ end
59
+ ```
60
+
61
+ ### 2. Add Routes
62
+
63
+ ```ruby
64
+ # config/routes.rb
65
+ post '/whatsapp/webhook', to: 'whatsapp#webhook' # Meta Cloud API
66
+ post '/whatsapp/twilio/webhook', to: 'whatsapp_twilio#webhook' # Twilio API
67
+ ```
68
+
69
+ ### 3. Create a Flow
70
+
71
+ ```ruby
72
+ # app/flow_chat/welcome_flow.rb
73
+ class WelcomeFlow < FlowChat::Flow
74
+ def main_page
75
+ name = app.screen(:name) do |prompt|
76
+ prompt.ask "Hello! What's your name?",
77
+ transform: ->(input) { input.strip.titleize }
78
+ end
79
+
80
+ choice = app.screen(:main_menu) do |prompt|
81
+ prompt.select "Hi #{name}! How can I help?", {
82
+ "info" => "📋 Get Information",
83
+ "support" => "🆘 Contact Support",
84
+ "feedback" => "💬 Give Feedback"
85
+ }
86
+ end
87
+
88
+ case choice
89
+ when "info"
90
+ show_info
91
+ when "support"
92
+ contact_support
93
+ when "feedback"
94
+ collect_feedback
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def show_info
101
+ app.say "📍 Located at 123 Main Street\n🕒 Hours: Mon-Fri 9AM-6PM"
102
+ end
103
+
104
+ def contact_support
105
+ app.say "📞 Call us at (555) 123-4567\n📧 Email: support@example.com"
106
+ end
107
+
108
+ def collect_feedback
109
+ rating = app.screen(:rating) do |prompt|
110
+ prompt.select "Rate our service:", ["⭐", "⭐⭐", "⭐⭐⭐", "⭐⭐⭐⭐", "⭐⭐⭐⭐⭐"]
111
+ end
112
+
113
+ app.say "Thank you for your #{rating} rating! 🙏"
114
+ end
115
+ end
116
+ ```
117
+
118
+ ## 🔧 Configuration
119
+
120
+ ### Meta Cloud API Configuration
121
+
122
+ #### Option 1: Rails Credentials (Recommended)
123
+
124
+ ```bash
125
+ rails credentials:edit
126
+ ```
127
+
128
+ ```yaml
129
+ whatsapp:
130
+ access_token: "your_access_token"
131
+ phone_number_id: "your_phone_number_id"
132
+ verify_token: "your_verify_token"
133
+ app_id: "your_app_id"
134
+ app_secret: "your_app_secret"
135
+ business_account_id: "your_business_account_id"
136
+ skip_signature_validation: false # Set to true only for development
137
+ ```
138
+
139
+ #### Option 2: Environment Variables
140
+
141
+ ```bash
142
+ export WHATSAPP_ACCESS_TOKEN="your_access_token"
143
+ export WHATSAPP_PHONE_NUMBER_ID="your_phone_number_id"
144
+ export WHATSAPP_VERIFY_TOKEN="your_verify_token"
145
+ export WHATSAPP_APP_ID="your_app_id"
146
+ export WHATSAPP_APP_SECRET="your_app_secret"
147
+ export WHATSAPP_BUSINESS_ACCOUNT_ID="your_business_account_id"
148
+ export WHATSAPP_SKIP_SIGNATURE_VALIDATION="false"
149
+ ```
150
+
151
+ ### Twilio WhatsApp Configuration
152
+
153
+ #### Option 1: Rails Credentials (Recommended)
154
+
155
+ ```bash
156
+ rails credentials:edit
157
+ ```
158
+
159
+ ```yaml
160
+ twilio_whatsapp:
161
+ account_sid: "your_twilio_account_sid"
162
+ auth_token: "your_twilio_auth_token"
163
+ phone_number: "+15551234567" # Your Twilio WhatsApp number
164
+ skip_signature_validation: false # Set to true only for development
165
+ ```
166
+
167
+ #### Option 2: Environment Variables
168
+
169
+ ```bash
170
+ export TWILIO_ACCOUNT_SID="your_twilio_account_sid"
171
+ export TWILIO_AUTH_TOKEN="your_twilio_auth_token"
172
+ export TWILIO_WHATSAPP_PHONE_NUMBER="+15551234567"
173
+ export TWILIO_WHATSAPP_SKIP_SIGNATURE_VALIDATION="false"
174
+ ```
175
+
176
+ ### Gateway Comparison
177
+
178
+ | Feature | Meta Cloud API | Twilio |
179
+ |---------|----------------|--------|
180
+ | **Interactive Elements** | Native buttons/lists | Text fallback |
181
+ | **Webhook Format** | JSON | Form-encoded |
182
+ | **Signature Validation** | X-Hub-Signature-256 (SHA256) | X-Twilio-Signature (SHA1) |
183
+ | **Response Format** | JSON | TwiML XML |
184
+ | **Setup Complexity** | Moderate (Meta Business) | Simple (Twilio account) |
185
+ | **Features** | Full WhatsApp features | Basic messaging |
186
+ | **Cost** | Per conversation | Per message |
187
+
188
+ ### Option 3: Programmatic Configuration
189
+
190
+ ```ruby
191
+ # Meta Cloud API
192
+ cloud_config = FlowChat::Whatsapp::Configuration.new(:my_account)
193
+ cloud_config.access_token = "your_access_token"
194
+ cloud_config.phone_number_id = "your_phone_number_id"
195
+ cloud_config.verify_token = "your_verify_token"
196
+ cloud_config.app_secret = "your_app_secret"
197
+
198
+ processor = FlowChat::Processor.new(self) do |config|
199
+ config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, cloud_config
200
+ config.use_session_store FlowChat::Session::CacheSessionStore
201
+ end
202
+
203
+ # Twilio WhatsApp
204
+ twilio_config = FlowChat::Whatsapp::TwilioConfiguration.new(:twilio_account)
205
+ twilio_config.account_sid = "your_account_sid"
206
+ twilio_config.auth_token = "your_auth_token"
207
+ twilio_config.phone_number = "+15551234567"
208
+
209
+ processor = FlowChat::Processor.new(self) do |config|
210
+ config.use_gateway FlowChat::Whatsapp::Gateway::Twilio, twilio_config
211
+ config.use_session_store FlowChat::Session::CacheSessionStore
212
+ end
213
+ ```
214
+
215
+ ## 📱 Interactive Elements
216
+
217
+ ### Buttons (Up to 3)
218
+
219
+ FlowChat automatically uses buttons for 3 or fewer choices:
220
+
221
+ ```ruby
222
+ def main_menu
223
+ choice = app.screen(:menu) do |prompt|
224
+ prompt.select "What would you like to do?", {
225
+ "balance" => "💰 Check Balance",
226
+ "transfer" => "📤 Transfer Money",
227
+ "history" => "📜 View History"
228
+ }
229
+ end
230
+
231
+ handle_choice(choice)
232
+ end
233
+ ```
234
+
235
+ ### Lists (4+ Items)
236
+
237
+ FlowChat automatically uses interactive lists for more than 3 choices:
238
+
239
+ ```ruby
240
+ def product_menu
241
+ product = app.screen(:products) do |prompt|
242
+ prompt.select "Choose a product:", {
243
+ "laptop" => "💻 Laptop - $999",
244
+ "phone" => "📱 Smartphone - $599",
245
+ "tablet" => "📱 Tablet - $399",
246
+ "watch" => "⌚ Smartwatch - $299",
247
+ "headphones" => "🎧 Headphones - $199"
248
+ }
249
+ end
250
+
251
+ show_product_details(product)
252
+ end
253
+ ```
254
+
255
+ Lists support:
256
+ - Up to 10 items per section
257
+ - Automatic pagination for more items
258
+ - Title truncation (24 chars) with full description (72 chars)
259
+
260
+ ## 🎨 Rich Media Support
261
+
262
+ ### Sending Media in Flows
263
+
264
+ ```ruby
265
+ def product_showcase
266
+ # Image with prompt
267
+ feedback = app.screen(:product_feedback) do |prompt|
268
+ prompt.ask "What do you think of this product?",
269
+ media: {
270
+ type: :image,
271
+ url: "https://example.com/product.jpg"
272
+ }
273
+ end
274
+
275
+ # Document response
276
+ app.say "Thanks! Here's the product catalog:",
277
+ media: {
278
+ type: :document,
279
+ url: "https://example.com/catalog.pdf",
280
+ filename: "product_catalog.pdf"
281
+ }
282
+ end
283
+
284
+ def video_tutorial
285
+ app.say "Watch this tutorial:",
286
+ media: {
287
+ type: :video,
288
+ url: "https://example.com/tutorial.mp4"
289
+ }
290
+ end
291
+ ```
292
+
293
+ ### Media Types Supported
294
+
295
+ | Type | Usage | Options |
296
+ |------|-------|---------|
297
+ | `:image` | Photos, screenshots | `url`, `caption` |
298
+ | `:document` | PDFs, docs, files | `url`, `caption`, `filename` |
299
+ | `:audio` | Voice messages, music | `url`, `caption` |
300
+ | `:video` | Video files | `url`, `caption` |
301
+ | `:sticker` | Stickers/animated | `url` (no caption) |
302
+
303
+ ### Buttons with Media Headers
304
+
305
+ ```ruby
306
+ def media_menu
307
+ choice = app.screen(:options) do |prompt|
308
+ prompt.select "Choose an option:",
309
+ {
310
+ "details" => "📋 More Details",
311
+ "buy" => "🛒 Buy Now",
312
+ "share" => "📤 Share"
313
+ },
314
+ media: {
315
+ type: :image,
316
+ url: "https://example.com/product.jpg"
317
+ }
318
+ end
319
+ end
320
+ ```
321
+
322
+ ### Handling Incoming Media
323
+
324
+ ```ruby
325
+ def handle_media_upload
326
+ if app.media
327
+ media_type = app.media["type"]
328
+ media_id = app.media["id"]
329
+
330
+ case media_type
331
+ when "image"
332
+ app.say "Thanks for the image! Processing..."
333
+ process_image(media_id)
334
+ when "document"
335
+ app.say "Document received. Reviewing..."
336
+ process_document(media_id)
337
+ when "audio"
338
+ app.say "Got your voice message!"
339
+ process_audio(media_id)
340
+ end
341
+ else
342
+ app.say "Please send a photo of your receipt."
343
+ end
344
+ end
345
+ ```
346
+
347
+ ## 📤 WhatsApp Client API
348
+
349
+ The `FlowChat::Whatsapp::Client` provides a comprehensive API for sending messages outside of flows and integrating with other systems.
350
+
351
+ ### Client Initialization
352
+
353
+ ```ruby
354
+ # From credentials/environment variables
355
+ config = FlowChat::Whatsapp::Configuration.from_credentials
356
+ client = FlowChat::Whatsapp::Client.new(config)
357
+
358
+ # Using named configuration
359
+ config = FlowChat::Whatsapp::Configuration.get(:my_account)
360
+ client = FlowChat::Whatsapp::Client.new(config)
361
+ ```
362
+
363
+ ### Text Messages
364
+
365
+ ```ruby
366
+ # Simple text message
367
+ client.send_text("+1234567890", "Hello! How can I help you today?")
368
+ ```
369
+
370
+ ### Interactive Messages
371
+
372
+ ```ruby
373
+ # Interactive buttons (up to 3)
374
+ buttons = [
375
+ {id: "option1", title: "View Orders"},
376
+ {id: "option2", title: "Track Package"},
377
+ {id: "option3", title: "Contact Support"}
378
+ ]
379
+ client.send_buttons("+1234567890", "What would you like to do?", buttons)
380
+
381
+ # Interactive lists (4+ options)
382
+ sections = [
383
+ {
384
+ title: "Products",
385
+ rows: [
386
+ {id: "laptop", title: "💻 Laptop", description: "High-performance laptop - $999"},
387
+ {id: "phone", title: "📱 Smartphone", description: "Latest model smartphone - $599"},
388
+ {id: "tablet", title: "📱 Tablet", description: "10-inch tablet - $399"}
389
+ ]
390
+ },
391
+ {
392
+ title: "Accessories",
393
+ rows: [
394
+ {id: "headphones", title: "🎧 Headphones", description: "Wireless headphones - $199"},
395
+ {id: "case", title: "📱 Phone Case", description: "Protective case - $29"}
396
+ ]
397
+ }
398
+ ]
399
+ client.send_list("+1234567890", "Choose a product:", sections, "Browse")
400
+ ```
401
+
402
+ ### Media Messages
403
+
404
+ ```ruby
405
+ # Send image
406
+ client.send_image("+1234567890", "https://example.com/image.jpg", "Check out our new product!")
407
+
408
+ # Send document
409
+ client.send_document(
410
+ "+1234567890",
411
+ "https://example.com/catalog.pdf",
412
+ "Here's our product catalog",
413
+ "catalog.pdf" # optional filename
414
+ )
415
+
416
+ # Send video
417
+ client.send_video("+1234567890", "https://example.com/tutorial.mp4", "Watch this tutorial")
418
+
419
+ # Send audio
420
+ client.send_audio("+1234567890", "https://example.com/message.mp3")
421
+
422
+ # Send sticker (no caption support)
423
+ client.send_sticker("+1234567890", "https://example.com/sticker.webp")
424
+ ```
425
+
426
+ ### Media Upload & Download
427
+
428
+ ```ruby
429
+ # Upload local file and get media ID
430
+ media_id = client.upload_media("/path/to/file.pdf", "application/pdf", "document.pdf")
431
+
432
+ # Upload from IO object
433
+ File.open("/path/to/image.jpg", "rb") do |file|
434
+ media_id = client.upload_media(file, "image/jpeg", "photo.jpg")
435
+ end
436
+
437
+ # Use uploaded media ID in messages
438
+ client.send_document("+1234567890", media_id, "Here's your document")
439
+
440
+ # Get media URL from ID
441
+ media_url = client.get_media_url(media_id)
442
+
443
+ # Download media content
444
+ media_content = client.download_media(media_id)
445
+ File.write("/tmp/downloaded_file", media_content, mode: "wb")
446
+ ```
447
+
448
+ ### Template Messages
449
+
450
+ ```ruby
451
+ # Send template message (for conversation initiation)
452
+ components = [
453
+ {
454
+ type: "body",
455
+ parameters: [
456
+ {type: "text", text: "John Doe"},
457
+ {type: "text", text: "ORD-12345"}
458
+ ]
459
+ }
460
+ ]
461
+
462
+ client.send_template(
463
+ "+1234567890",
464
+ "order_confirmation",
465
+ components,
466
+ "en_US"
467
+ )
468
+ ```
469
+
470
+ ### Read Receipts & Typing Indicator
471
+
472
+ Mark an inbound message as read and optionally show a typing indicator while your application processes a slow request (LLM call, lookup, etc.). WhatsApp Cloud API ties the typing indicator to the mark-as-read call — there is no separate typing endpoint.
473
+
474
+ ```ruby
475
+ client = FlowChat::Whatsapp::Client.new(config)
476
+
477
+ # Just mark as read
478
+ client.mark_as_read("wamid.ABC123")
479
+
480
+ # Mark as read AND show "typing…" in the user's chat
481
+ client.mark_as_read("wamid.ABC123", typing: true)
482
+
483
+ # Convenience equivalent of the line above
484
+ client.indicate_typing("wamid.ABC123")
485
+ ```
486
+
487
+ The typing indicator auto-dismisses on the next outbound message or after ~25 seconds. There is **no stop-typing call** — send the real reply and the indicator clears.
488
+
489
+ `message_id` must be the id of an inbound user message; you cannot show a typing indicator without an associated inbound message.
490
+
491
+ ### Complete Service Example
492
+
493
+ ```ruby
494
+ # Service for sending notifications
495
+ class WhatsappNotificationService
496
+ def initialize(config_name = :default)
497
+ @config = FlowChat::Whatsapp::Configuration.get(config_name)
498
+ @client = FlowChat::Whatsapp::Client.new(@config)
499
+ end
500
+
501
+ def send_order_confirmation(phone_number, order_id)
502
+ # Send confirmation message
503
+ @client.send_text(phone_number, "Order ##{order_id} confirmed! 🛍️")
504
+
505
+ # Send invoice document
506
+ @client.send_document(
507
+ phone_number,
508
+ "https://storage.example.com/invoices/#{order_id}.pdf",
509
+ "Your invoice is ready",
510
+ "invoice_#{order_id}.pdf"
511
+ )
512
+
513
+ # Send interactive buttons
514
+ @client.send_buttons(phone_number, "What would you like to do next?", [
515
+ {id: "track", title: "Track Order"},
516
+ {id: "support", title: "Contact Support"},
517
+ {id: "invoice", title: "View Invoice"}
518
+ ])
519
+ end
520
+
521
+ def send_media_gallery(phone_number, images)
522
+ images.each_with_index do |image_url, index|
523
+ @client.send_image(phone_number, image_url, "Image #{index + 1}")
524
+ end
525
+ end
526
+
527
+ def send_welcome_message(phone_number, user_name)
528
+ # Upload and send personalized image
529
+ File.open("welcome_images/#{user_name.downcase}.jpg", "rb") do |file|
530
+ media_id = @client.upload_media(file, "image/jpeg", "welcome.jpg")
531
+ @client.send_image(phone_number, media_id, "Welcome #{user_name}! 🎉")
532
+ end
533
+ end
534
+ end
535
+
536
+ # Usage in controllers or background jobs
537
+ service = WhatsappNotificationService.new(:main_account)
538
+ service.send_order_confirmation("+1234567890", "ORD-123")
539
+ ```
540
+
541
+ ## 🏗️ Template Manager
542
+
543
+ The `FlowChat::Whatsapp::TemplateManager` provides utilities for managing WhatsApp message templates, which are required for initiating conversations with users.
544
+
545
+ ### Template Manager Setup
546
+
547
+ ```ruby
548
+ # Using default configuration
549
+ template_manager = FlowChat::Whatsapp::TemplateManager.new
550
+
551
+ # Using specific configuration
552
+ config = FlowChat::Whatsapp::Configuration.get(:my_account)
553
+ template_manager = FlowChat::Whatsapp::TemplateManager.new(config)
554
+ ```
555
+
556
+ ### Sending Template Messages
557
+
558
+ ```ruby
559
+ # Basic template message
560
+ template_manager.send_template(
561
+ to: "+1234567890",
562
+ template_name: "hello_world",
563
+ language: "en_US",
564
+ components: []
565
+ )
566
+
567
+ # Template with parameters
568
+ components = [
569
+ {
570
+ type: "body",
571
+ parameters: [
572
+ {type: "text", text: "John Doe"},
573
+ {type: "text", text: "Order #12345"}
574
+ ]
575
+ }
576
+ ]
577
+
578
+ template_manager.send_template(
579
+ to: "+1234567890",
580
+ template_name: "order_update",
581
+ language: "en_US",
582
+ components: components
583
+ )
584
+ ```
585
+
586
+ ### Pre-built Template Methods
587
+
588
+ ```ruby
589
+ # Send welcome template (uses Meta's default hello_world template)
590
+ template_manager.send_welcome_template(
591
+ to: "+1234567890",
592
+ name: "John Doe" # Optional personalization
593
+ )
594
+
595
+ # Send notification template
596
+ template_manager.send_notification_template(
597
+ to: "+1234567890",
598
+ message: "Your order has shipped! Track it with the link below.",
599
+ button_text: "Track Package" # Optional quick reply button
600
+ )
601
+ ```
602
+
603
+ ### Template Management
604
+
605
+ ```ruby
606
+ # Create a new template (requires Meta approval)
607
+ template_manager.create_template(
608
+ name: "order_confirmation",
609
+ category: "UTILITY", # AUTHENTICATION, MARKETING, UTILITY
610
+ language: "en_US",
611
+ components: [
612
+ {
613
+ type: "HEADER",
614
+ format: "TEXT",
615
+ text: "Order Confirmed"
616
+ },
617
+ {
618
+ type: "BODY",
619
+ text: "Hi {{1}}, your order {{2}} has been confirmed and will arrive by {{3}}."
620
+ },
621
+ {
622
+ type: "FOOTER",
623
+ text: "Thank you for shopping with us!"
624
+ },
625
+ {
626
+ type: "BUTTONS",
627
+ buttons: [
628
+ {
629
+ type: "QUICK_REPLY",
630
+ text: "Track Order"
631
+ },
632
+ {
633
+ type: "QUICK_REPLY",
634
+ text: "Cancel Order"
635
+ }
636
+ ]
637
+ }
638
+ ]
639
+ )
640
+
641
+ # List all templates
642
+ templates = template_manager.list_templates
643
+ templates["data"].each do |template|
644
+ puts "Template: #{template["name"]} - Status: #{template["status"]}"
645
+ end
646
+
647
+ # Check template status
648
+ status = template_manager.template_status("template_id_here")
649
+ puts "Template status: #{status["status"]}" # PENDING, APPROVED, REJECTED
650
+ ```
651
+
652
+ ### Template Categories
653
+
654
+ - **AUTHENTICATION**: One-time passwords, account verification
655
+ - **MARKETING**: Promotional messages, newsletters (requires opt-in)
656
+ - **UTILITY**: Order updates, appointment reminders, account notifications
657
+
658
+ ### Template Components
659
+
660
+ ```ruby
661
+ # Complete template structure example
662
+ components = [
663
+ # Header (optional)
664
+ {
665
+ type: "HEADER",
666
+ format: "TEXT", # or IMAGE, VIDEO, DOCUMENT
667
+ text: "Your Receipt"
668
+ },
669
+
670
+ # Body (required)
671
+ {
672
+ type: "BODY",
673
+ text: "Hi {{1}}, here's your receipt for order {{2}} totaling {{3}}."
674
+ },
675
+
676
+ # Footer (optional)
677
+ {
678
+ type: "FOOTER",
679
+ text: "Questions? Reply to this message."
680
+ },
681
+
682
+ # Buttons (optional)
683
+ {
684
+ type: "BUTTONS",
685
+ buttons: [
686
+ {
687
+ type: "QUICK_REPLY",
688
+ text: "Download PDF"
689
+ },
690
+ {
691
+ type: "URL",
692
+ text: "View Online",
693
+ url: "https://example.com/receipt/{{1}}"
694
+ },
695
+ {
696
+ type: "PHONE_NUMBER",
697
+ text: "Call Support",
698
+ phone_number: "+1234567890"
699
+ }
700
+ ]
701
+ }
702
+ ]
703
+ ```
704
+
705
+ ### Integration with Services
706
+
707
+ ```ruby
708
+ class OrderNotificationService
709
+ def initialize
710
+ @template_manager = FlowChat::Whatsapp::TemplateManager.new
711
+ end
712
+
713
+ def notify_order_confirmed(order)
714
+ @template_manager.send_template(
715
+ to: order.customer_phone,
716
+ template_name: "order_confirmation",
717
+ language: order.customer_locale || "en_US",
718
+ components: [
719
+ {
720
+ type: "body",
721
+ parameters: [
722
+ {type: "text", text: order.customer_name},
723
+ {type: "text", text: order.id},
724
+ {type: "text", text: order.estimated_delivery.strftime("%B %d")}
725
+ ]
726
+ }
727
+ ]
728
+ )
729
+ end
730
+
731
+ def notify_order_shipped(order)
732
+ @template_manager.send_notification_template(
733
+ to: order.customer_phone,
734
+ message: "Great news! Order #{order.id} has shipped and is on its way.",
735
+ button_text: "Track Package"
736
+ )
737
+ end
738
+ end
739
+ ```
740
+
741
+ ## 📁 Media Upload & Download
742
+
743
+ ### Uploading Files
744
+
745
+ ```ruby
746
+ # Upload local file
747
+ client = FlowChat::Whatsapp::Client.new(config)
748
+ media_id = client.upload_media("/path/to/file.pdf", "application/pdf", "document.pdf")
749
+
750
+ # Upload from IO
751
+ File.open("/path/to/image.jpg", "rb") do |file|
752
+ media_id = client.upload_media(file, "image/jpeg", "photo.jpg")
753
+ end
754
+
755
+ # Use uploaded media ID
756
+ client.send_document("+1234567890", media_id, "Here's your document")
757
+ ```
758
+
759
+ ### Downloading Media
760
+
761
+ ```ruby
762
+ # Get media URL from ID
763
+ media_url = client.get_media_url(media_id)
764
+
765
+ # Download media content
766
+ media_content = client.download_media(media_id)
767
+
768
+ # Save to file
769
+ File.write("/tmp/downloaded_media", media_content, mode: "wb")
770
+ ```
771
+
772
+ ## ⚙️ Processing Modes
773
+
774
+ FlowChat supports three distinct message processing modes to handle different deployment scenarios and performance requirements. Configure the mode in your initializer:
775
+
776
+ ```ruby
777
+ # config/initializers/flowchat.rb
778
+ FlowChat::Config.whatsapp.message_handling_mode = :inline # Default
779
+ ```
780
+
781
+ ### Mode Comparison
782
+
783
+ | Feature | Inline | Background | Simulator |
784
+ |---------|--------|------------|-----------|
785
+ | **Response Speed** | Immediate | Async (queued) | Immediate |
786
+ | **Webhook Timeout Risk** | High | None | None |
787
+ | **Scalability** | Limited | High | N/A (testing only) |
788
+ | **Setup Complexity** | Simple | Moderate | Simple |
789
+ | **Production Ready** | Yes | Yes | No (development only) |
790
+ | **Debugging** | Easy | Moderate | Easy |
791
+ | **Message Delivery** | Real WhatsApp | Real WhatsApp | Simulated |
792
+
793
+ ### Inline Mode (Default)
794
+
795
+ Messages are processed synchronously:
796
+
797
+ ```ruby
798
+ FlowChat::Config.whatsapp.message_handling_mode = :inline
799
+ ```
800
+
801
+ **Pros**: Simple, immediate responses
802
+ **Cons**: Webhook timeouts for slow operations
803
+
804
+ ### Simulator Mode
805
+
806
+ Returns response data instead of sending via WhatsApp API, perfect for development and testing:
807
+
808
+ ```ruby
809
+ FlowChat::Config.whatsapp.message_handling_mode = :simulator
810
+ ```
811
+
812
+ #### Simulator Features
813
+
814
+ - **No API calls**: Messages are not sent to WhatsApp
815
+ - **Response inspection**: Returns full message payload in HTTP response
816
+ - **Flow debugging**: Test conversation flows without real phone numbers
817
+ - **No webhook validation**: Bypasses signature validation for easier testing
818
+ - **Fast iteration**: Immediate feedback without waiting for WhatsApp delivery
819
+
820
+ #### Simulator Response Format
821
+
822
+ Instead of sending to WhatsApp, the simulator returns JSON with the message payload:
823
+
824
+ ```json
825
+ {
826
+ "mode": "simulator",
827
+ "message_sent": true,
828
+ "response_data": {
829
+ "messaging_product": "whatsapp",
830
+ "to": "+1234567890",
831
+ "type": "interactive",
832
+ "interactive": {
833
+ "type": "button",
834
+ "body": {"text": "What would you like to do?"},
835
+ "action": {
836
+ "buttons": [
837
+ {"type": "reply", "reply": {"id": "option1", "title": "View Orders"}},
838
+ {"type": "reply", "reply": {"id": "option2", "title": "Track Package"}}
839
+ ]
840
+ }
841
+ }
842
+ },
843
+ "config_used": "default",
844
+ "timestamp": "2024-01-15T10:30:00Z"
845
+ }
846
+ ```
847
+
848
+ #### Using Simulator in Development
849
+
850
+ ```ruby
851
+ # Enable simulator conditionally
852
+ processor = FlowChat::Processor.new(self) do |config|
853
+ config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
854
+ config.use_session_store FlowChat::Session::CacheSessionStore
855
+ end
856
+
857
+ # Set mode globally in development
858
+ FlowChat::Config.whatsapp.message_handling_mode = Rails.env.development? ? :simulator : :inline
859
+
860
+ # Or enable per-request (useful for testing)
861
+ # Add ?simulator=true to webhook URL for one-off testing
862
+ ```
863
+
864
+ #### Testing with Simulator
865
+
866
+ ```ruby
867
+ # test/integration/whatsapp_simulator_test.rb
868
+ class WhatsappSimulatorTest < ActionDispatch::IntegrationTest
869
+ def setup
870
+ # Force simulator mode for tests
871
+ @original_mode = FlowChat::Config.whatsapp.message_handling_mode
872
+ FlowChat::Config.whatsapp.message_handling_mode = :simulator
873
+ end
874
+
875
+ def teardown
876
+ FlowChat::Config.whatsapp.message_handling_mode = @original_mode
877
+ end
878
+
879
+ def test_welcome_flow_buttons
880
+ # Simulate WhatsApp webhook
881
+ webhook_data = {
882
+ entry: [{
883
+ changes: [{
884
+ value: {
885
+ messages: [{
886
+ from: "1234567890",
887
+ id: "msg_123",
888
+ type: "text",
889
+ text: { body: "hello" }
890
+ }],
891
+ contacts: [{
892
+ profile: { name: "Test User" }
893
+ }]
894
+ }
895
+ }]
896
+ }]
897
+ }
898
+
899
+ post "/whatsapp/webhook", params: webhook_data
900
+
901
+ assert_response :success
902
+ response_json = JSON.parse(response.body)
903
+
904
+ assert_equal "simulator", response_json["mode"]
905
+ assert_equal "interactive", response_json["response_data"]["type"]
906
+ assert response_json["response_data"]["interactive"]["action"]["buttons"].present?
907
+ end
908
+ end
909
+ ```
910
+
911
+ **Pros**: Perfect for testing, fast development, no API costs, detailed response inspection
912
+ **Cons**: Messages aren't actually sent, doesn't test real WhatsApp behavior
913
+
914
+ ## 🔒 Security & Validation
915
+
916
+ ### Webhook Signature Validation
917
+
918
+ FlowChat automatically validates WhatsApp webhook signatures using HMAC-SHA256 to ensure requests are genuinely from WhatsApp and haven't been tampered with.
919
+
920
+ #### How Signature Validation Works
921
+
922
+ ```ruby
923
+ # 1. Extract signature from X-Hub-Signature-256 header
924
+ # 2. Calculate HMAC-SHA256 hash of request body using app_secret
925
+ # 3. Compare calculated signature with provided signature using secure comparison
926
+ # 4. Reject request if signatures don't match
927
+ ```
928
+
929
+ #### Production Configuration (Required)
930
+
931
+ ```ruby
932
+ # config/credentials/production.yml
933
+ whatsapp:
934
+ app_secret: "your_whatsapp_app_secret_from_meta"
935
+ # ... other settings
936
+
937
+ # The gateway automatically validates signatures
938
+ processor = FlowChat::Processor.new(self) do |config|
939
+ config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
940
+ # Signature validation is enabled by default
941
+ end
942
+ ```
943
+
944
+ #### Development Configuration Options
945
+
946
+ ```ruby
947
+ # Option 1: Use real app_secret (recommended for staging)
948
+ config = FlowChat::Whatsapp::Configuration.new
949
+ config.app_secret = "your_whatsapp_app_secret"
950
+ config.skip_signature_validation = false # Default behavior
951
+
952
+ # Option 2: Explicitly disable validation (development only)
953
+ config = FlowChat::Whatsapp::Configuration.new
954
+ config.app_secret = nil # Not required when disabled
955
+ config.skip_signature_validation = true # Must be explicitly set to true
956
+
957
+ # Option 3: Environment-based configuration
958
+ config = FlowChat::Whatsapp::Configuration.new
959
+ config.app_secret = Rails.env.production? ? "real_secret" : nil
960
+ config.skip_signature_validation = !Rails.env.production?
961
+ ```
962
+
963
+ #### Configuration Error Handling
964
+
965
+ The gateway will raise `FlowChat::Whatsapp::ConfigurationError` if:
966
+
967
+ ```ruby
968
+ # Missing app_secret with validation enabled (default)
969
+ # Error: "WhatsApp app_secret is required for webhook signature validation"
970
+
971
+ # Invalid webhook signature received
972
+ # Returns HTTP 401 Unauthorized
973
+
974
+ # Valid configuration examples:
975
+ config.app_secret = "secret"; config.skip_signature_validation = false # ✅ Secure
976
+ config.app_secret = nil; config.skip_signature_validation = true # ✅ Explicitly disabled
977
+ config.app_secret = "secret"; config.skip_signature_validation = true # ✅ Secret provided but validation disabled
978
+
979
+ # Invalid configuration:
980
+ config.app_secret = nil; config.skip_signature_validation = false # ❌ Error
981
+ ```
982
+
983
+ #### Security Implementation Details
984
+
985
+ ```ruby
986
+ # The gateway uses secure comparison to prevent timing attacks
987
+ def valid_webhook_signature?(request)
988
+ # Extract signature from X-Hub-Signature-256 header
989
+ signature_header = request.headers["X-Hub-Signature-256"]
990
+ expected_signature = signature_header.sub("sha256=", "")
991
+
992
+ # Calculate HMAC-SHA256 of request body
993
+ body = request.body.read
994
+ calculated_signature = OpenSSL::HMAC.hexdigest(
995
+ OpenSSL::Digest.new("SHA256"),
996
+ @config.app_secret,
997
+ body
998
+ )
999
+
1000
+ # Use secure comparison to prevent timing attacks
1001
+ ActiveSupport::SecurityUtils.secure_compare(expected_signature, calculated_signature)
1002
+ end
1003
+ ```
1004
+
1005
+ ⚠️ **Security Warning**: Never disable signature validation in production environments.
1006
+
1007
+ ### Error Handling
1008
+
1009
+ ```ruby
1010
+ def webhook
1011
+ processor = FlowChat::Processor.new(self) do |config|
1012
+ config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
1013
+ config.use_session_store FlowChat::Session::CacheSessionStore
1014
+ end
1015
+
1016
+ processor.run WelcomeFlow, :main_page
1017
+ rescue FlowChat::Whatsapp::ConfigurationError => e
1018
+ Rails.logger.error "WhatsApp configuration error: #{e.message}"
1019
+ head :internal_server_error
1020
+ rescue => e
1021
+ Rails.logger.error "WhatsApp webhook error: #{e.message}"
1022
+ head :internal_server_error
1023
+ end
1024
+ ```
1025
+
1026
+ ### Configuration Validation
1027
+
1028
+ FlowChat will raise a `ConfigurationError` if:
1029
+ - `app_secret` is missing and validation is not explicitly disabled
1030
+ - Required credentials are missing
1031
+ - Invalid webhook signatures are received
1032
+
1033
+ ## 🏢 Multi-Tenant Support
1034
+
1035
+ ### Named Configurations
1036
+
1037
+ Register configurations by name for different accounts:
1038
+
1039
+ ```ruby
1040
+ # config/initializers/whatsapp_configs.rb
1041
+ tenant_a_config = FlowChat::Whatsapp::Configuration.new(:tenant_a)
1042
+ tenant_a_config.access_token = "tenant_a_token"
1043
+ tenant_a_config.phone_number_id = "tenant_a_phone"
1044
+ tenant_a_config.verify_token = "tenant_a_verify"
1045
+ tenant_a_config.app_secret = "tenant_a_secret"
1046
+
1047
+ tenant_b_config = FlowChat::Whatsapp::Configuration.new(:tenant_b)
1048
+ tenant_b_config.access_token = "tenant_b_token"
1049
+ tenant_b_config.phone_number_id = "tenant_b_phone"
1050
+ tenant_b_config.verify_token = "tenant_b_verify"
1051
+ tenant_b_config.app_secret = "tenant_b_secret"
1052
+ ```
1053
+
1054
+ ### Configuration API
1055
+
1056
+ The configuration system provides several methods for managing named configurations:
1057
+
1058
+ ```ruby
1059
+ # Check if a configuration exists
1060
+ FlowChat::Whatsapp::Configuration.exists?(:tenant_a) # => true/false
1061
+
1062
+ # Get configuration by name (raises error if not found)
1063
+ config = FlowChat::Whatsapp::Configuration.get(:tenant_a)
1064
+
1065
+ # Get configuration with fallback
1066
+ config = FlowChat::Whatsapp::Configuration.get(:tenant_a) rescue FlowChat::Whatsapp::Configuration.from_credentials
1067
+
1068
+ # Register configuration programmatically
1069
+ config = FlowChat::Whatsapp::Configuration.new(:dynamic_tenant)
1070
+ config.access_token = "token_here"
1071
+ config.phone_number_id = "phone_id_here"
1072
+ # ... other settings
1073
+ ```
1074
+
1075
+ ### Using Named Configurations
1076
+
1077
+ ```ruby
1078
+ class MultiTenantWhatsappController < ApplicationController
1079
+ def webhook
1080
+ tenant = determine_tenant(request)
1081
+
1082
+ # Verify configuration exists
1083
+ unless FlowChat::Whatsapp::Configuration.exists?(tenant)
1084
+ Rails.logger.error "No WhatsApp configuration found for tenant: #{tenant}"
1085
+ head :not_found
1086
+ return
1087
+ end
1088
+
1089
+ whatsapp_config = FlowChat::Whatsapp::Configuration.get(tenant)
1090
+
1091
+ processor = FlowChat::Processor.new(self) do |config|
1092
+ config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, whatsapp_config
1093
+ config.use_session_store FlowChat::Session::CacheSessionStore
1094
+ # Use tenant-specific session boundaries
1095
+ config.use_session_config(
1096
+ boundaries: [:flow, :platform, :tenant],
1097
+ identifier: :msisdn,
1098
+ tenant: tenant
1099
+ )
1100
+ end
1101
+
1102
+ processor.run WelcomeFlow, :main_page
1103
+ end
1104
+
1105
+ private
1106
+
1107
+ def determine_tenant(request)
1108
+ # Multiple strategies for tenant detection
1109
+ if request.subdomain.present?
1110
+ request.subdomain.to_sym
1111
+ elsif request.path.start_with?('/api/')
1112
+ # Extract from API path: /api/v1/tenants/acme/whatsapp
1113
+ request.path.split('/')[4]&.to_sym
1114
+ elsif request.headers['X-Tenant-ID'].present?
1115
+ request.headers['X-Tenant-ID'].to_sym
1116
+ else
1117
+ :default
1118
+ end
1119
+ end
1120
+ end
1121
+ ```
1122
+
1123
+ ### Background Jobs with Multi-Tenancy
1124
+
1125
+ ```ruby
1126
+ class WhatsappMessageJob < ApplicationJob
1127
+ include FlowChat::Whatsapp::SendJobSupport
1128
+
1129
+ # SendJobSupport automatically handles configuration resolution
1130
+ # It will use send_data[:config_name] if provided, otherwise fallback to default
1131
+
1132
+ def on_whatsapp_send_success(send_data, result)
1133
+ # Log with tenant context
1134
+ tenant = send_data[:config_name] || :default
1135
+ Rails.logger.info "Message sent for tenant #{tenant}: #{result["messages"]&.first&.dig("id")}"
1136
+ end
1137
+ end
1138
+ ```
1139
+
1140
+ ### Dynamic Configuration Management
1141
+
1142
+ ```ruby
1143
+ class TenantConfigurationService
1144
+ def self.setup_tenant(tenant_name, credentials)
1145
+ config = FlowChat::Whatsapp::Configuration.new(tenant_name)
1146
+ config.access_token = credentials[:access_token]
1147
+ config.phone_number_id = credentials[:phone_number_id]
1148
+ config.verify_token = credentials[:verify_token]
1149
+ config.app_secret = credentials[:app_secret]
1150
+ config.business_account_id = credentials[:business_account_id]
1151
+
1152
+ # Validate configuration before registering
1153
+ unless config.valid?
1154
+ raise "Invalid WhatsApp configuration for tenant: #{tenant_name}"
1155
+ end
1156
+
1157
+ Rails.logger.info "WhatsApp configuration registered for tenant: #{tenant_name}"
1158
+ end
1159
+
1160
+ def self.remove_tenant(tenant_name)
1161
+ # Note: The current implementation doesn't provide a removal method
1162
+ # This would need to be added to the Configuration class
1163
+ Rails.logger.info "TODO: Remove configuration for tenant: #{tenant_name}"
1164
+ end
1165
+ end
1166
+
1167
+ # Usage in tenant onboarding
1168
+ TenantConfigurationService.setup_tenant(:new_client, {
1169
+ access_token: "EAAxxxxxxxxx",
1170
+ phone_number_id: "1234567890",
1171
+ verify_token: "my_verify_token",
1172
+ app_secret: "app_secret_here",
1173
+ business_account_id: "business_account_id"
1174
+ })
1175
+ ```
1176
+
1177
+ ## 🧪 Testing & Development
1178
+
1179
+ ### Simulator Mode
1180
+
1181
+ Enable the simulator for testing during development:
1182
+
1183
+ ```ruby
1184
+ # Enable simulator in development
1185
+ processor = FlowChat::Processor.new(self, enable_simulator: Rails.env.development?) do |config|
1186
+ config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
1187
+ config.use_session_store FlowChat::Session::CacheSessionStore
1188
+ end
1189
+ ```
1190
+
1191
+ The simulator provides:
1192
+ - Web interface for testing flows
1193
+ - No actual WhatsApp API calls
1194
+ - Response data returned in HTTP response
1195
+ - Secure cookie-based authentication
1196
+
1197
+ ### Testing Flows
1198
+
1199
+ ```ruby
1200
+ # test/flow_chat/welcome_flow_test.rb
1201
+ require 'test_helper'
1202
+
1203
+ class WelcomeFlowTest < ActiveSupport::TestCase
1204
+ include FlowChat::TestHelpers
1205
+
1206
+ def test_main_page_flow
1207
+ # Simulate WhatsApp input
1208
+ session = create_test_session
1209
+ app = create_test_app(session: session)
1210
+ flow = WelcomeFlow.new(app)
1211
+
1212
+ # Test the flow
1213
+ simulate_input(app, "John")
1214
+ simulate_input(app, "info")
1215
+
1216
+ response = flow.main_page
1217
+
1218
+ assert_includes response.last, "Located at 123 Main Street"
1219
+ end
1220
+ end
1221
+ ```
1222
+
1223
+ ## 📊 Instrumentation & Monitoring
1224
+
1225
+ FlowChat includes built-in instrumentation for WhatsApp events:
1226
+
1227
+ ```ruby
1228
+ # Events automatically tracked:
1229
+ # - FlowChat::Events::MESSAGE_RECEIVED
1230
+ # - FlowChat::Events::MESSAGE_SENT
1231
+ # - FlowChat::Events::MEDIA_UPLOAD
1232
+ # - FlowChat::Events::WEBHOOK_VERIFIED
1233
+ # - FlowChat::Events::WEBHOOK_FAILED
1234
+
1235
+ # Custom instrumentation
1236
+ FlowChat.instrument("custom.whatsapp.event", {
1237
+ user_id: app.user_id,
1238
+ action: "custom_action",
1239
+ metadata: { key: "value" }
1240
+ })
1241
+ ```
1242
+
1243
+ ## 🚨 Troubleshooting
1244
+
1245
+ ### Common Issues
1246
+
1247
+ **1. Configuration Error: app_secret required**
1248
+ ```
1249
+ WhatsApp app_secret is required for webhook signature validation.
1250
+ ```
1251
+ **Solution**: Configure `WHATSAPP_APP_SECRET` or disable validation explicitly.
1252
+
1253
+ **2. Invalid webhook signature**
1254
+ ```
1255
+ Invalid webhook signature received
1256
+ ```
1257
+ **Solution**: Verify your `app_secret` matches your WhatsApp app configuration.
1258
+
1259
+ **3. Media upload fails**
1260
+ ```
1261
+ Media upload failed: Invalid mime type
1262
+ ```
1263
+ **Solution**: Ensure MIME type is correctly specified and supported.
1264
+
1265
+ **4. Background job class not found**
1266
+ ```
1267
+ Background mode requested but no WhatsappMessageJob found
1268
+ ```
1269
+ **Solution**: Create the background job class or use inline mode.
1270
+
1271
+ ### Debug Mode
1272
+
1273
+ Enable debug logging:
1274
+
1275
+ ```ruby
1276
+ # config/environments/development.rb
1277
+ config.log_level = :debug
1278
+
1279
+ # This will log:
1280
+ # - Webhook signature validation
1281
+ # - Message parsing and extraction
1282
+ # - API request/response details
1283
+ # - Configuration loading
1284
+ ```
1285
+
1286
+ ### Environment-Specific Configuration
1287
+
1288
+ ```ruby
1289
+ # config/initializers/flowchat.rb
1290
+ case Rails.env
1291
+ when 'development'
1292
+ FlowChat::Config.whatsapp.message_handling_mode = :simulator
1293
+ # Skip signature validation for easier testing
1294
+
1295
+ when 'test'
1296
+ FlowChat::Config.whatsapp.message_handling_mode = :simulator
1297
+
1298
+ when 'staging'
1299
+ FlowChat::Config.whatsapp.message_handling_mode = :inline
1300
+ # Full security validation
1301
+
1302
+ when 'production'
1303
+ FlowChat::Config.whatsapp.message_handling_mode = :background
1304
+ FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
1305
+ # Maximum security and scalability
1306
+ end
1307
+ ```
1308
+
1309
+ ## 🎯 Best Practices
1310
+
1311
+ ### 1. Message Design
1312
+ - Keep button titles under 20 characters
1313
+ - Use clear, actionable text
1314
+ - Include relevant emojis for visual appeal
1315
+ - Test across different devices
1316
+
1317
+ ### 2. Media Usage
1318
+ - Use appropriate file sizes (< 16MB for documents)
1319
+ - Provide descriptive captions
1320
+ - Use supported formats (JPEG, PNG for images; PDF for documents)
1321
+ - Consider bandwidth limitations
1322
+
1323
+ ### 3. Flow Design
1324
+ - Handle media uploads gracefully
1325
+ - Provide clear error messages
1326
+ - Use progressive disclosure for complex flows
1327
+ - Test with real user scenarios
1328
+
1329
+ ### 4. Security
1330
+ - Always validate webhooks in production
1331
+ - Use environment variables for secrets
1332
+ - Log security events appropriately
1333
+ - Monitor for unusual patterns
1334
+
1335
+ ### 5. Performance
1336
+ - Use background processing for heavy operations
1337
+ - Cache frequently used data
1338
+ - Monitor response times
1339
+ - Handle timeouts gracefully
1340
+
1341
+ ## 📚 API Reference
1342
+
1343
+ ### Core Classes
1344
+
1345
+ - `FlowChat::Whatsapp::Gateway::CloudApi` - Meta Cloud API gateway implementation
1346
+ - `FlowChat::Whatsapp::Gateway::Twilio` - Twilio WhatsApp gateway implementation
1347
+ - `FlowChat::Whatsapp::Client` - Meta Cloud API client for out-of-band messaging
1348
+ - `FlowChat::Whatsapp::TwilioClient` - Twilio API client for out-of-band messaging
1349
+ - `FlowChat::Whatsapp::Configuration` - Meta Cloud API configuration management
1350
+ - `FlowChat::Whatsapp::TwilioConfiguration` - Twilio configuration management
1351
+ - `FlowChat::Whatsapp::Renderer` - Message rendering logic
1352
+
1353
+ ### Client Methods
1354
+
1355
+ | Method | Description | Parameters |
1356
+ |--------|-------------|------------|
1357
+ | `send_message(to, response)` | Send FlowChat response format | phone, response array |
1358
+ | `send_text(to, text)` | Send text message | phone, message |
1359
+ | `send_buttons(to, text, buttons)` | Send interactive buttons | phone, text, button array |
1360
+ | `send_list(to, text, sections, button_text)` | Send interactive list | phone, text, sections, button text |
1361
+ | `send_template(to, name, components, language)` | Send template message | phone, template name, components, language |
1362
+ | `send_image(to, url_or_id, caption, mime_type)` | Send image | phone, url/media ID, caption, mime type |
1363
+ | `send_document(to, url_or_id, caption, filename, mime_type)` | Send document | phone, url/media ID, caption, filename, mime type |
1364
+ | `send_video(to, url_or_id, caption, mime_type)` | Send video | phone, url/media ID, caption, mime type |
1365
+ | `send_audio(to, url_or_id, mime_type)` | Send audio | phone, url/media ID, mime type |
1366
+ | `send_sticker(to, url_or_id, mime_type)` | Send sticker | phone, url/media ID, mime type |
1367
+ | `upload_media(file_path_or_io, mime_type, filename)` | Upload file and get media ID | file path/IO, mime type, filename |
1368
+ | `get_media_url(media_id)` | Get media URL from ID | media ID |
1369
+ | `download_media(media_id)` | Download media content | media ID |
1370
+ | `build_message_payload(response, to)` | Build WhatsApp API payload | FlowChat response, phone number |
1371
+
1372
+ ### Template Manager Methods
1373
+
1374
+ | Method | Description | Parameters |
1375
+ |--------|-------------|------------|
1376
+ | `send_template(to:, template_name:, language:, components:)` | Send template message | named parameters |
1377
+ | `send_welcome_template(to:, name:)` | Send welcome template | phone, optional name |
1378
+ | `send_notification_template(to:, message:, button_text:)` | Send notification template | phone, message, optional button |
1379
+ | `create_template(name:, category:, language:, components:)` | Create new template | template details |
1380
+ | `list_templates()` | List all templates | none |
1381
+ | `template_status(template_id)` | Get template status | template ID |
1382
+
1383
+ ### Configuration Methods
1384
+
1385
+ | Method | Description | Parameters |
1386
+ |--------|-------------|------------|
1387
+ | `Configuration.from_credentials()` | Load from Rails credentials/ENV | none |
1388
+ | `Configuration.new(name)` | Create named configuration | configuration name |
1389
+ | `Configuration.register(name, config)` | Register configuration | name, config object |
1390
+ | `Configuration.get(name)` | Get configuration by name | configuration name |
1391
+ | `Configuration.exists?(name)` | Check if config exists | configuration name |
1392
+
1393
+ ---
1394
+
1395
+ This comprehensive guide covers everything you need to build sophisticated WhatsApp integrations with FlowChat. For more examples and advanced patterns, check the [examples directory](../../examples/) in the FlowChat repository.