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.
- checksums.yaml +4 -4
- data/.cliff.toml +74 -0
- data/.github/workflows/ci.yml +2 -3
- data/.github/workflows/release.yml +56 -0
- data/.standard.yml +4 -0
- data/CHANGELOG.md +22 -0
- data/CLAUDE.md +327 -0
- data/CONTRIBUTING.md +134 -0
- data/Gemfile +1 -0
- data/README.md +290 -105
- data/Rakefile +5 -1
- data/SECURITY.md +42 -349
- data/docs/architecture.md +510 -0
- data/docs/async-background-processing.md +298 -0
- data/docs/configuration.md +556 -226
- data/docs/factory-pattern.md +355 -0
- data/docs/gateway-context-variables.md +171 -0
- data/docs/gateway-development.md +723 -0
- data/docs/getting-started.md +429 -0
- data/docs/instrumentation.md +264 -153
- data/docs/platforms/telegram.md +1013 -0
- data/docs/platforms/ussd.md +693 -0
- data/docs/platforms/whatsapp.md +1395 -0
- data/docs/testing.md +243 -365
- data/examples/custom_session_id_example.rb +119 -0
- data/examples/http_controller.rb +154 -0
- data/examples/intercom_configuration_example.rb +118 -0
- data/examples/intercom_controller.rb +194 -0
- data/examples/multi_tenant_whatsapp_controller.rb +4 -4
- data/examples/simulator_controller.rb +21 -1
- data/examples/ussd_controller.rb +10 -10
- data/examples/whatsapp_controller.rb +2 -2
- data/flow_chat.gemspec +4 -0
- data/lib/flow_chat/{base_app.rb → app.rb} +28 -9
- data/lib/flow_chat/async_job.rb +176 -0
- data/lib/flow_chat/config.rb +23 -28
- data/lib/flow_chat/{base_executor.rb → executor.rb} +7 -12
- data/lib/flow_chat/factory.rb +94 -0
- data/lib/flow_chat/gateway_async_support.rb +106 -0
- data/lib/flow_chat/generic_async_job.rb +30 -0
- data/lib/flow_chat/http/gateway/simple.rb +125 -0
- data/lib/flow_chat/http/renderer.rb +41 -0
- data/lib/flow_chat/instrumentation/setup.rb +1 -1
- data/lib/flow_chat/instrumentation.rb +25 -0
- data/lib/flow_chat/intercom/client.rb +155 -0
- data/lib/flow_chat/intercom/configuration.rb +149 -0
- data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
- data/lib/flow_chat/intercom/renderer.rb +71 -0
- data/lib/flow_chat/phone_number_util.rb +49 -0
- data/lib/flow_chat/processor.rb +188 -0
- data/lib/flow_chat/renderers/markdown_support.rb +58 -0
- data/lib/flow_chat/session/cache_session_store.rb +1 -17
- data/lib/flow_chat/session/middleware.rb +43 -26
- data/lib/flow_chat/simulator/controller.rb +17 -5
- data/lib/flow_chat/simulator/views/simulator.html.erb +220 -8
- data/lib/flow_chat/telegram/client.rb +240 -0
- data/lib/flow_chat/telegram/configuration.rb +118 -0
- data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
- data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
- data/lib/flow_chat/telegram/renderer.rb +125 -0
- data/lib/flow_chat/telegram.rb +7 -0
- data/lib/flow_chat/ussd/gateway/nalo.rb +27 -11
- data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
- data/lib/flow_chat/ussd/renderer.rb +1 -1
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +144 -13
- data/lib/flow_chat/whatsapp/configuration.rb +1 -1
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +144 -106
- data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
- data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
- data/lib/flow_chat/whatsapp/renderer.rb +145 -11
- data/lib/flow_chat.rb +11 -1
- data/lib/tasks/release.rake +165 -0
- metadata +88 -21
- data/docs/flows.md +0 -320
- data/docs/images/simulator.png +0 -0
- data/docs/media.md +0 -153
- data/docs/sessions.md +0 -433
- data/docs/ussd-setup.md +0 -322
- data/docs/whatsapp-setup.md +0 -162
- data/examples/whatsapp_message_job.rb +0 -113
- data/lib/flow_chat/base_processor.rb +0 -145
- data/lib/flow_chat/session/rails_session_store.rb +0 -68
- data/lib/flow_chat/ussd/app.rb +0 -6
- data/lib/flow_chat/ussd/gateway/nsano.rb +0 -98
- data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
- data/lib/flow_chat/ussd/processor.rb +0 -39
- data/lib/flow_chat/whatsapp/app.rb +0 -29
- data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
- data/lib/flow_chat/whatsapp/processor.rb +0 -32
- 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.
|