Skip to content

Repository files navigation

Kapso Ruby Client

Kapso API Ruby SDK

Gem VersionRubyLicense: MIT

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.

Features

  • 🚀 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

Installation

Add this line to your application's Gemfile:

gem'kapso-client-ruby'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install kapso-client-ruby

Rails Integration

For Rails applications, use the built-in generator to set up everything automatically:

rails generate kapso_client_ruby:install

This 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.

Quick Start

Basic Setup

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}"

Using Kapso Proxy (for enhanced features)

# 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)

API Reference

Flows

Create and manage interactive WhatsApp Flows for rich data collection:

Create and Deploy a Flow

# 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]}"

Send a Flow Message

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')

Handle Flow Webhooks

# 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

Manage Flows

# 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')

Messages

Send various types of messages with the Messages resource:

Text Messages

# 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)

Media Messages

# 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'})

Group Messaging

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)

Location Messages

# 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')

Interactive Messages

# 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'}]}])

Interactive CTA URL Messages

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 characters
  • display_text: Max 20 characters
  • url: Must be valid HTTP/HTTPS URL
  • footer_text: Max 60 characters (optional)
  • header: Supports text, image, video, or document

Catalog Messages

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 characters
  • thumbnail_product_retailer_id: Product SKU to display as thumbnail (required)
  • footer_text: Max 60 characters (optional)

Template Messages

# 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'}]}])

Message Reactions

# 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)

Message Status

# 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')

Media Management

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)

Template Management

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')

Advanced Features (Kapso Proxy)

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'])

Configuration

Global Configuration

KapsoClientRuby.configuredo |config|
config.debug=trueconfig.timeout=60config.open_timeout=10config.max_retries=3config.retry_delay=1.0end

Client Configuration

client=KapsoClientRuby::Client.new(access_token: 'token',debug: true,timeout: 30,logger: Logger.new('whatsapp.log'))

Debug Logging

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)

Error Handling

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

Error Categories

  • :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

Automatic Retry Logic

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 retryendend

Webhook Handling

Handle 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']}"endend

Testing

Run 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 rubocop

Testing with VCR

The 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_presentendend

Examples

See the examples directory for comprehensive usage examples:

Requirements

  • Ruby >= 2.7.0
  • Faraday >= 2.0
  • A WhatsApp Business Account with Cloud API access
  • Valid access token from Meta or Kapso API key

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/gokapso/whatsapp-cloud-api-ruby.

Development Setup

git clone https://github.com/gokapso/whatsapp-cloud-api-ruby.git
cd whatsapp-cloud-api-ruby
bundle install

Running Tests

# 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

Code Style

# Check style
bundle exec rubocop
# Auto-fix issues
bundle exec rubocop -A

License

This gem is available as open source under the terms of the MIT License.

Support

Changelog

See CHANGELOG.md for version history and updates.


Built with ❤️ for the Kapso team

About

Unofficial Ruby SDK API for kapso.ai platform

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages