A comprehensive Ruby client library for the WhatsApp Business Cloud API. This SDK provides a complete interface for sending messages, managing media, templates, and more, with built-in error handling, retry logic, and debug capabilities.
- 🚀 Complete API Coverage: All Kapso Cloud API endpoints supported
- 📱 Rich Message Types: Text, media, templates, interactive messages, and more
- 🔐 Dual Authentication: Meta Graph API and Kapso Proxy support
- 🛡️ Smart Error Handling: Comprehensive error categorization and retry logic
- 📊 Advanced Features: Message history, analytics, and contact management (via Kapso)
- 🔍 Debug Support: Detailed logging and request/response tracing
- 📚 Type Safety: Structured response objects and validation
- ⚡ Performance: HTTP connection pooling and efficient request handling
- 🛤️ Rails Integration: First-class Rails support with generators, service classes, and background jobs
Add this line to your application's Gemfile:
gem'kapso-client-ruby'And then execute:
$ bundle installOr install it yourself as:
$ gem install kapso-client-rubyFor Rails applications, use the built-in generator to set up everything automatically:
rails generate kapso_client_ruby:installThis creates:
- Configuration initializer
- Webhook controller
- Service class for messaging
- Background job examples
- Routes for webhooks
See the Rails Integration Guide for detailed Rails-specific documentation.
require'kapso_client_api'# Initialize client with Meta Graph API access tokenclient=KapsoClientRuby::Client.new(access_token: 'your_access_token')# Send a text messageresponse=client.messages.send_text(phone_number_id: 'your_phone_number_id',to: '+1234567890',body: 'Hello from Ruby!')puts"Message sent: #{response.messages.first.id}"# Initialize client with Kapso API key for enhanced featureskapso_client=KapsoClientRuby::Client.new(kapso_api_key: 'your_kapso_api_key',base_url: 'https://app.kapso.ai/api/meta')# Access message history and analyticsmessages=kapso_client.messages.query(phone_number_id: 'your_phone_number_id',direction: 'inbound',limit: 10)Create and manage interactive WhatsApp Flows for rich data collection:
# Idempotent deployment (recommended)deployment=client.flows.deploy(business_account_id: 'your_business_id',name: 'appointment_booking',categories: ['APPOINTMENT_BOOKING'],flow_json: {version: '3.0',screens: [{id: 'APPOINTMENT',title: 'Book Appointment',layout: {type: 'SingleColumnLayout',children: [{type: 'Form',name: 'appointment_form',children: [{type: 'TextInput',name: 'customer_name',label: 'Full Name',required: true},{type: 'DatePicker',name: 'appointment_date',label: 'Preferred Date',required: true},{type: 'Footer',label: 'Submit',on_click_action: {name: 'complete',payload: {customer_name: '${form.customer_name}',appointment_date: '${form.appointment_date}'}}}]}]}}]})puts"Flow ID: #{deployment[:id]}"require'securerandom'client.messages.send_flow(phone_number_id: 'phone_id',to: '+1234567890',flow_id: 'your_flow_id',flow_cta: 'Book Appointment',flow_token: SecureRandom.uuid,header: {type: 'text',text: 'Appointment Booking'},body_text: 'Book your appointment in just a few taps!',footer_text: 'Available slots fill up fast')# In your webhook endpointdefhandle_flow_webhook(encrypted_request)private_key=OpenSSL::PKey::RSA.new(File.read('private_key.pem'))# Decrypt incoming Flow eventflow_event=client.flows.receive_flow_event(encrypted_request: encrypted_request,private_key: private_key)# Process form datacaseflow_event.actionwhen'INIT'response_data={version: flow_event.version,screen: 'APPOINTMENT',data: {available_dates: ['2024-01-15','2024-01-16']}}when'data_exchange'# Save appointment datacustomer_name=flow_event.data['customer_name']appointment_date=flow_event.data['appointment_date']response_data={version: flow_event.version,screen: 'SUCCESS',data: {success: true}}end# Encrypt and return responseclient.flows.respond_to_flow(response_data: response_data,private_key: private_key)end# List all Flowsflows=client.flows.list(business_account_id: 'business_id')# Get Flow detailsflow=client.flows.get(flow_id: 'flow_id')# Update Flowclient.flows.update(flow_id: 'flow_id',categories: ['APPOINTMENT_BOOKING','CUSTOMER_SUPPORT'])# Update Flow JSONasset_response=client.flows.update_asset(flow_id: 'flow_id',asset: flow_json_hash)# Publish Flowclient.flows.publish(flow_id: 'flow_id')# Get preview URLpreview=client.flows.preview(flow_id: 'flow_id')putspreview.preview_url# Deprecate Flowclient.flows.deprecate(flow_id: 'flow_id')# Delete Flowclient.flows.delete(flow_id: 'flow_id')Send various types of messages with the Messages resource:
# Simple text messageclient.messages.send_text(phone_number_id: 'phone_id',to: '+1234567890',body: 'Hello World!')# Text with URL previewclient.messages.send_text(phone_number_id: 'phone_id',to: '+1234567890',body: 'Check this out: https://example.com',preview_url: true)# Send imageclient.messages.send_image(phone_number_id: 'phone_id',to: '+1234567890',image: {link: 'https://example.com/image.jpg',caption: 'Beautiful sunset'})# Send documentclient.messages.send_document(phone_number_id: 'phone_id',to: '+1234567890',document: {id: 'media_id',# or link: 'https://...'filename: 'report.pdf',caption: 'Monthly report'})# Send audioclient.messages.send_audio(phone_number_id: 'phone_id',to: '+1234567890',audio: {id: 'audio_media_id'})# Send voice note (OGG/OPUS format recommended)client.messages.send_audio(phone_number_id: 'phone_id',to: '+1234567890',audio: {link: 'https://example.com/voice-note.ogg'},voice: true# Marks as voice note)# Send videoclient.messages.send_video(phone_number_id: 'phone_id',to: '+1234567890',video: {link: 'https://example.com/video.mp4',caption: 'Tutorial video'})Send messages to WhatsApp groups by setting recipient_type: 'group':
# Send text to groupclient.messages.send_text(phone_number_id: 'phone_id',to: '120363XXXXXXXXX@g.us',# Group ID formatbody: 'Hello everyone!',recipient_type: 'group')# Send image to groupclient.messages.send_image(phone_number_id: 'phone_id',to: '120363XXXXXXXXX@g.us',image: {link: 'https://example.com/team-photo.jpg'},caption: 'Team photo from our event',recipient_type: 'group')Note: Group messaging works with all message types (text, images, videos, documents, interactive messages, etc.)
Group ID Format:XXXXXXXXX@g.us (WhatsApp group identifier)
# Send locationclient.messages.send_location(phone_number_id: 'phone_id',to: '+1234567890',latitude: 37.7749,longitude: -122.4194,name: 'Our Office',address: '123 Main St, San Francisco, CA')# Request user's locationclient.messages.send_interactive_location_request(phone_number_id: 'phone_id',to: '+1234567890',body_text: 'Please share your location for delivery',footer_text: 'Your privacy is important to us')# Request location with headerclient.messages.send_interactive_location_request(phone_number_id: 'phone_id',to: '+1234567890',header: {type: 'image',image: {link: 'https://example.com/map-icon.png'}},body_text: 'Share your location to find the nearest store',footer_text: 'Tap to share')# Button interactive messageclient.messages.send_interactive_buttons(phone_number_id: 'phone_id',to: '+1234567890',body_text: 'Choose an option:',buttons: [{type: 'reply',reply: {id: 'option_1',title: 'Option 1'}},{type: 'reply',reply: {id: 'option_2',title: 'Option 2'}}],header: {type: 'text',text: 'Menu'})# List interactive messageclient.messages.send_interactive_list(phone_number_id: 'phone_id',to: '+1234567890',body_text: 'Please select from the list:',button_text: 'View Options',sections: [{title: 'Section 1',rows: [{id: 'item_1',title: 'Item 1',description: 'Description 1'}]}])Send messages with Call-to-Action buttons that open URLs:
# With image headerclient.messages.send_interactive_cta_url(phone_number_id: 'phone_id',to: '+1234567890',header: {type: 'image',image: {link: 'https://example.com/banner.jpg'}},body_text: 'Get 25% off your first purchase!',display_text: 'Shop Now',# Max 20 charactersurl: 'https://shop.example.com?utm_source=whatsapp',footer_text: 'Limited time offer')# With text headerclient.messages.send_interactive_cta_url(phone_number_id: 'phone_id',to: '+1234567890',header: {type: 'text',text: 'Special Offer'},body_text: 'Join our exclusive members club!',display_text: 'Join Now',url: 'https://members.example.com/signup')# With video or document headerclient.messages.send_interactive_cta_url(phone_number_id: 'phone_id',to: '+1234567890',header: {type: 'video',video: {link: 'https://example.com/demo.mp4'}},body_text: 'Watch our product in action!',display_text: 'Learn More',url: 'https://example.com/product')Validations:
body_text: Max 1024 charactersdisplay_text: Max 20 charactersurl: Must be valid HTTP/HTTPS URLfooter_text: Max 60 characters (optional)header: Supports text, image, video, or document
Send your product catalog directly in WhatsApp:
client.messages.send_interactive_catalog_message(phone_number_id: 'phone_id',to: '+1234567890',body_text: 'Browse our entire product catalog!',thumbnail_product_retailer_id: 'SKU-001',# Product SKU for thumbnailfooter_text: 'Tap to explore')Parameters:
body_text: Max 1024 charactersthumbnail_product_retailer_id: Product SKU to display as thumbnail (required)footer_text: Max 60 characters (optional)
# Simple templateclient.messages.send_template(phone_number_id: 'phone_id',to: '+1234567890',name: 'hello_world',language: 'en_US')# Template with parametersclient.messages.send_template(phone_number_id: 'phone_id',to: '+1234567890',name: 'appointment_reminder',language: 'en_US',components: [{type: 'body',parameters: [{type: 'text',text: 'John Doe'},{type: 'text',text: 'Tomorrow at 2 PM'}]}])# Add reactionclient.messages.send_reaction(phone_number_id: 'phone_id',to: '+1234567890',message_id: 'message_to_react_to',emoji: '👍')# Remove reactionclient.messages.send_reaction(phone_number_id: 'phone_id',to: '+1234567890',message_id: 'message_to_react_to',emoji: nil)# Mark message as readclient.messages.mark_read(phone_number_id: 'phone_id',message_id: 'message_id')# Send typing indicatorclient.messages.send_typing_indicator(phone_number_id: 'phone_id',to: '+1234567890')Handle media uploads, downloads, and management:
# Upload mediaupload_response=client.media.upload(phone_number_id: 'phone_id',type: 'image',file: '/path/to/image.jpg')media_id=upload_response.id# Get media metadatametadata=client.media.get(media_id: media_id)puts"File size: #{metadata.file_size} bytes"puts"MIME type: #{metadata.mime_type}"# Download mediacontent=client.media.download(media_id: media_id,as: :binary)# Save media to fileclient.media.save_to_file(media_id: media_id,filepath: '/path/to/save/file.jpg')# Delete mediaclient.media.delete(media_id: media_id)Create, manage, and use message templates:
# List templatestemplates=client.templates.list(business_account_id: 'your_business_id',status: 'APPROVED')# Create marketing templatetemplate_data=client.templates.build_marketing_template(name: 'summer_sale',language: 'en_US',body: 'Hi {{1}}! Our summer sale is here with {{2}} off!',header: {type: 'HEADER',format: 'TEXT',text: 'Summer Sale 🌞'},footer: 'Limited time offer',buttons: [{type: 'URL',text: 'Shop Now',url: 'https://shop.example.com'}],body_example: {body_text: [['John','25%']]})response=client.templates.create(business_account_id: 'your_business_id',
**template_data)# Create authentication templateauth_template=client.templates.build_authentication_template(name: 'verify_code',language: 'en_US',ttl_seconds: 300)client.templates.create(business_account_id: 'your_business_id',
**auth_template)# Delete templateclient.templates.delete(business_account_id: 'your_business_id',name: 'old_template',language: 'en_US')Access enhanced features with Kapso proxy:
# Initialize Kapso clientkapso_client=KapsoClientRuby::Client.new(kapso_api_key: 'your_kapso_key',base_url: 'https://app.kapso.ai/api/meta')# Message historymessages=kapso_client.messages.query(phone_number_id: 'phone_id',direction: 'inbound',since: '2024-01-01T00:00:00Z',limit: 50)# Conversation managementconversations=kapso_client.conversations.list(phone_number_id: 'phone_id',status: 'active')conversation=kapso_client.conversations.get(conversation_id: conversations.data.first.id)# Update conversation statuskapso_client.conversations.update_status(conversation_id: conversation.id,status: 'archived')# Contact managementcontacts=kapso_client.contacts.list(phone_number_id: 'phone_id',limit: 100)# Update contact metadatakapso_client.contacts.update(phone_number_id: 'phone_id',wa_id: 'contact_wa_id',metadata: {tags: ['premium','customer'],source: 'website'})# Search contactsresults=kapso_client.contacts.search(phone_number_id: 'phone_id',query: 'john',search_in: ['profile_name','phone_number'])KapsoClientRuby.configuredo |config|
config.debug=trueconfig.timeout=60config.open_timeout=10config.max_retries=3config.retry_delay=1.0endclient=KapsoClientRuby::Client.new(access_token: 'token',debug: true,timeout: 30,logger: Logger.new('whatsapp.log'))Enable debug logging to see detailed HTTP requests and responses:
# Enable debug modeclient=KapsoClientRuby::Client.new(access_token: 'token',debug: true)# Custom loggerlogger=Logger.new(STDOUT)logger.level=Logger::DEBUGclient=KapsoClientRuby::Client.new(access_token: 'token',logger: logger)The SDK provides comprehensive error handling with detailed categorization:
beginclient.messages.send_text(phone_number_id: 'phone_id',to: 'invalid_number',body: 'Test')rescueKapsoClientRuby::Errors::GraphApiError=>eputs"Error: #{e.message}"puts"Category: #{e.category}"puts"HTTP Status: #{e.http_status}"puts"Code: #{e.code}"# Check error typecasee.categorywhen:authorizationputs"Authentication failed - check your access token"when:parameterputs"Invalid parameter - check phone number format"when:throttlingputs"Rate limited - wait before retrying"ife.retry_hint[:retry_after_ms]sleep(e.retry_hint[:retry_after_ms] / 1000.0)endwhen:templateputs"Template error - check template name and parameters"when:mediaputs"Media error - check file format and size"end# Check retry recommendationscasee.retry_hint[:action]when:retryputs"Safe to retry this request"when:retry_afterputs"Retry after specified delay: #{e.retry_hint[:retry_after_ms]}ms"when:do_not_retryputs"Do not retry - permanent error"when:fix_and_retryputs"Fix the request and retry"when:refresh_tokenputs"Access token needs to be refreshed"endend:authorization- Authentication and token errors:permission- Permission and access errors:parameter- Invalid parameters or format errors:throttling- Rate limiting errors:template- Template-related errors:media- Media upload/download errors:phone_registration- Phone number registration errors:integrity- Message integrity errors:business_eligibility- Business account eligibility errors:reengagement_window- 24-hour messaging window errors:waba_config- WhatsApp Business Account configuration errors:flow- WhatsApp Flow errors:synchronization- Data synchronization errors:server- Server-side errors:unknown- Unclassified errors
defsend_with_retry(client,max_retries=3)retries=0beginclient.messages.send_text(phone_number_id: 'phone_id',to: '+1234567890',body: 'Test message')rescueKapsoClientRuby::Errors::GraphApiError=>eretries += 1casee.retry_hint[:action]when:retryifretries <= max_retriessleep(retries * 2)# Exponential backoffretryendwhen:retry_afterife.retry_hint[:retry_after_ms] && retries <= max_retriessleep(e.retry_hint[:retry_after_ms] / 1000.0)retryendendraise# Re-raise if no retryendendHandle incoming webhooks from WhatsApp:
# Verify webhook signaturedefverify_webhook_signature(payload,signature,app_secret)require'openssl'sig_hash=signature.sub('sha256=','')expected_sig=OpenSSL::HMAC.hexdigest('sha256',app_secret,payload)sig_hash == expected_sigend# In your webhook endpointdefhandle_webhook(request)payload=request.body.readsignature=request.headers['X-Hub-Signature-256']unlessverify_webhook_signature(payload,signature,ENV['WHATSAPP_APP_SECRET'])return[401,{},['Unauthorized']]endwebhook_data=JSON.parse(payload)# Process webhook datawebhook_data['entry'].eachdo |entry|
entry['changes'].eachdo |change|
ifchange['field'] == 'messages'messages=change['value']['messages'] || []messages.eachdo |message|
handle_incoming_message(message)endendendend[200,{},['OK']]enddefhandle_incoming_message(message)casemessage['type']when'text'puts"Received text: #{message['text']['body']}"when'image'puts"Received image: #{message['image']['id']}"when'interactive'puts"Received interactive response: #{message['interactive']}"endendRun the test suite:
# Install development dependencies
bundle install
# Run tests
bundle exec rspec
# Run tests with coverage
bundle exec rspec --format documentation
# Run rubocop for style checking
bundle exec rubocopThe SDK includes VCR cassettes for testing without making real API calls:
# spec/spec_helper.rbrequire'vcr'VCR.configuredo |config|
config.cassette_library_dir='spec/vcr_cassettes'config.hook_into:webmockconfig.configure_rspec_metadata!# Filter sensitive dataconfig.filter_sensitive_data('<ACCESS_TOKEN>'){ENV['WHATSAPP_ACCESS_TOKEN']}config.filter_sensitive_data('<PHONE_NUMBER_ID>'){ENV['PHONE_NUMBER_ID']}end# In your testsRSpec.describe'Messages'doit'sends text message',:vcrdoclient=KapsoClientRuby::Client.new(access_token: 'test_token')response=client.messages.send_text(phone_number_id: 'test_phone_id',to: '+1234567890',body: 'Test message')expect(response.messages.first.id).tobe_presentendendSee the examples directory for comprehensive usage examples:
- Basic Messaging - Text, media, and template messages
- Media Management - Upload, download, and manage media
- Template Management - Create and manage templates
- Advanced Features - Kapso proxy features and analytics
- Ruby >= 2.7.0
- Faraday >= 2.0
- A WhatsApp Business Account with Cloud API access
- Valid access token from Meta or Kapso API key
Bug reports and pull requests are welcome on GitHub at https://github.com/gokapso/whatsapp-cloud-api-ruby.
git clone https://github.com/gokapso/whatsapp-cloud-api-ruby.git
cd whatsapp-cloud-api-ruby
bundle install# Run all tests
bundle exec rspec
# Run specific test file
bundle exec rspec spec/client_spec.rb
# Run with coverage
COVERAGE=true bundle exec rspec# Check style
bundle exec rubocop
# Auto-fix issues
bundle exec rubocop -AThis gem is available as open source under the terms of the MIT License.
- 📖 WhatsApp Cloud API Documentation
- 🌐 Kapso Platform for enhanced features
- 🐛 Issue Tracker
- 📧 Email: support@kapso.ai
See CHANGELOG.md for version history and updates.
Built with ❤️ for the Kapso team
