This guide will walk you through setting up FlowChat and building your first conversational application that works across multiple platforms.
- Ruby 2.3+ (Ruby 3.0+ recommended)
- Rails 6.0+ (Rails 7.0+ recommended)
- Basic understanding of Rails controllers and routing
Add FlowChat to your Rails application:
# Gemfile
gem 'flow_chat'bundle installLet's build a simple survey application that works across USSD, WhatsApp, Telegram, and HTTP APIs.
Create a new file app/flow_chat/survey_flow.rb:
class SurveyFlow < FlowChat::Flow
def start
# Welcome message that works on all platforms
name = app.screen(:name) do |prompt|
prompt.ask "Welcome to our survey! What's your name?",
validate: ->(input) {
return "Name must be at least 2 characters" if input.length < 2
nil
},
transform: ->(input) { input.strip.titleize }
end
# Rating question with platform-appropriate choices
rating = app.screen(:rating) do |prompt|
prompt.select "Hi #{name}! Rate our service:", {
"5" => "⭐⭐⭐⭐⭐ Excellent",
"4" => "⭐⭐⭐⭐ Good",
"3" => "⭐⭐⭐ Average",
"2" => "⭐⭐ Poor",
"1" => "⭐ Very Poor"
}
end
# Feedback collection
feedback = app.screen(:feedback) do |prompt|
prompt.ask "Any additional feedback?",
validate: ->(input) {
return "Feedback too short" if input.length < 5
nil
}
end
# Save the survey (your business logic)
save_survey(name, rating, feedback, app.msisdn)
# Thank you message
app.say "Thank you #{name}! Your feedback (#{rating}⭐) has been recorded."
end
private
def save_survey(name, rating, feedback, phone)
# Your database logic here
Rails.logger.info "Survey: #{name} (#{phone}) rated #{rating}/5: #{feedback}"
# Example: Save to database
# Survey.create!(
# name: name,
# rating: rating.to_i,
# feedback: feedback,
# phone: phone,
# platform: app.platform
# )
end
end# app/controllers/ussd_controller.rb
class UssdController < ApplicationController
skip_forgery_protection
def nalo_webhook
processor = FlowChat::Processor.new(self) do |config|
config.use_gateway FlowChat::Ussd::Gateway::Nalo
config.use_session_store FlowChat::Session::CacheSessionStore
# Optional: Configure USSD-specific settings
config.use_session_config(boundaries: [:flow, :platform])
end
processor.run SurveyFlow, :start
end
end# app/controllers/whatsapp_controller.rb
class WhatsappController < ApplicationController
skip_forgery_protection
def webhook
processor = FlowChat::Processor.new(self) do |config|
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
config.use_session_store FlowChat::Session::CacheSessionStore
end
processor.run SurveyFlow, :start
rescue => e
Rails.logger.error "WhatsApp error: #{e.message}"
head :internal_server_error
end
end# app/controllers/telegram_controller.rb
class TelegramController < ApplicationController
skip_forgery_protection
def webhook
processor = FlowChat::Processor.new(self) do |config|
config.use_gateway FlowChat::Telegram::Gateway::BotApi
config.use_session_store FlowChat::Session::CacheSessionStore
end
processor.run SurveyFlow, :start
rescue => e
Rails.logger.error { "Telegram error: #{e.message}" }
head :internal_server_error
end
end# app/controllers/api/chat_controller.rb
class Api::ChatController < ApplicationController
before_action :authenticate_api_user # Your auth logic
def message
processor = FlowChat::Processor.new(self) do |config|
config.use_gateway FlowChat::Http::Gateway::Simple
config.use_session_store FlowChat::Session::CacheSessionStore
# Session isolation per API user
config.use_session_config(identifier: :user_id)
end
processor.run SurveyFlow, :start
# Automatically returns JSON response
end
end# config/routes.rb
Rails.application.routes.draw do
# USSD routes
post '/ussd/nalo', to: 'ussd#nalo_webhook'
# WhatsApp routes
post '/whatsapp/webhook', to: 'whatsapp#webhook'
get '/whatsapp/webhook', to: 'whatsapp#verify' # For webhook verification
# Telegram routes
post '/telegram/webhook', to: 'telegram#webhook'
# API routes
namespace :api do
post '/chat/message', to: 'chat#message'
end
endAdd these to your .env file:
# WhatsApp Configuration
WHATSAPP_ACCESS_TOKEN=your_access_token
WHATSAPP_PHONE_NUMBER_ID=your_phone_number_id
WHATSAPP_VERIFY_TOKEN=your_verify_token
WHATSAPP_APP_SECRET=your_app_secret
# Telegram Configuration
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_SECRET_TOKEN=your_webhook_secret # Optional: for webhook validationCreate config/initializers/flow_chat.rb:
FlowChat::Config.logger = Rails.logger
FlowChat::Config.cache = Rails.cache
# USSD Configuration
FlowChat::Config.ussd.pagination_page_size = 160 # Adjust for your network
FlowChat::Config.ussd.pagination_next_option = "#"
FlowChat::Config.ussd.pagination_back_option = "0"
# WhatsApp Configuration
FlowChat::Config.whatsapp.message_handling_mode = :inline # or :backgroundCreate a simple test in rails console:
# Start a simulator session
simulator = FlowChat::Simulator.new(SurveyFlow, :start)
# Simulate the conversation
simulator.start
# => "Welcome to our survey! What's your name?"
simulator.send_message("John Doe")
# => "Hi John Doe! Rate our service:"
simulator.select_option("5")
# => "Any additional feedback?"
simulator.send_message("Great service, very helpful!")
# => "Thank you John Doe! Your feedback (5⭐) has been recorded."# Test the HTTP endpoint
curl -X POST http://localhost:3000/api/chat/message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your_api_token" \
-d '{
"user_id": "user123",
"input": "Hello"
}'Visit your Rails app with the simulator enabled:
# In development, add this to your controller
processor = FlowChat::Processor.new(self, enable_simulator: true) do |config|
config.use_gateway FlowChat::Ussd::Gateway::Nalo
# ... other config
endFlowChat uses a screen-based approach where each app.screen(:key) represents a step in your conversation:
- Automatic State Management: Each screen's result is automatically cached
- Navigation Stack: FlowChat tracks where users are in the flow
- Resume Capability: Users can return to where they left off
The same flow code works across all platforms because:
- Unified Prompts:
prompt.ask()andprompt.select()adapt to each platform - Context Normalization: All platforms provide consistent context (phone number, input, etc.)
- Flexible Rendering: Emojis show on WhatsApp, get stripped for USSD
Each platform can have multiple gateway implementations:
# USSD with different providers
config.use_gateway FlowChat::Ussd::Gateway::Nalo
# config.use_gateway FlowChat::Ussd::Gateway::Africaist
# config.use_gateway YourCompany::Ussd::Gateway::CustomProvider
# WhatsApp with different APIs
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
# config.use_gateway FlowChat::Whatsapp::Gateway::OnPremise
# config.use_gateway YourCompany::Whatsapp::Gateway::TwilioFlowChat provides flexible session configuration:
# Ephemeral sessions (restart on each request)
config.use_session_config(identifier: :request_id)
# Durable sessions (persist across timeouts using phone number)
config.use_durable_sessions
# Cross-platform sessions (same user, different platforms)
config.use_cross_platform_sessions
# Multi-tenant isolation
config.use_url_isolationNow that you have a basic multi-platform application running:
-
Explore Platform Features: Learn platform-specific capabilities
- USSD Development - Pagination, choice mapping
- WhatsApp Development - Rich media, templates
- Telegram Development - Inline keyboards, media, callbacks
- HTTP Development - API integration patterns
-
Advanced Topics:
- Session Management - Deep dive into session boundaries
- Middleware Development - Custom processing logic
- Gateway Development - Build your own platform support
-
Production Deployment:
- Configuration - Production-ready settings
- Background Jobs - Async processing for WhatsApp
- Testing - Comprehensive testing strategies
app.screen(:phone) do |prompt|
prompt.ask "Enter your phone number:",
validate: ->(input) {
return "Invalid phone format" unless input.match?(/^\+?[\d\s-()]+$/)
parsed = Phonelib.parse(input)
return "Invalid phone number" unless parsed.valid?
nil
},
transform: ->(input) { Phonelib.parse(input).e164 }
enddef main_menu
user_type = determine_user_type(app.msisdn)
if user_type == :premium
premium_menu
else
basic_menu
end
enddef payment_flow
begin
amount = app.screen(:amount) { |p| p.ask "Enter amount:" }
process_payment(amount)
app.say "Payment successful!"
rescue PaymentError => e
app.say "Payment failed: #{e.message}"
end
end-
Session Not Persisting
- Check your session store configuration
- Verify session boundaries match your use case
-
Gateway Errors
- Ensure environment variables are set correctly
- Check gateway-specific configuration requirements
-
Platform Differences
- Test flows on each platform's simulator
- Be aware of character limits (USSD) vs rich features (WhatsApp)
Enable comprehensive logging:
# config/initializers/flow_chat.rb
FlowChat::Config.logger.level = Logger::DEBUG
# In your flow
Rails.logger.debug "Current screen: #{app.navigation_stack.last}"
Rails.logger.debug "User input: #{app.input.inspect}"Ready to build more sophisticated flows? Continue with the Architecture Overview to understand FlowChat's design principles.