No release in over 3 years
A comprehensive Ruby SDK for the Kapso API with support for sending messages, managing media, templates, and more. Includes debug logging and comprehensive error handling.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

~> 2.0
~> 13.0
~> 3.0
~> 1.50
~> 0.22
~> 6.0
~> 3.18
~> 0.9

Runtime

 Project Readme

Kapso API Ruby SDK

Gem Version Ruby License: 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-api'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install kapso-client-api

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 token
client = KapsoClientRuby::Client.new(
  access_token: 'your_access_token'
)

# Send a text message
response = 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 features
kapso_client = KapsoClientRuby::Client.new(
  kapso_api_key: 'your_kapso_api_key',
  base_url: 'https://app.kapso.ai/api/meta'
)

# Access message history and analytics
messages = kapso_client.messages.query(
  phone_number_id: 'your_phone_number_id',
  direction: 'inbound',
  limit: 10
)

API Reference

Messages

Send various types of messages with the Messages resource:

Text Messages

# Simple text message
client.messages.send_text(
  phone_number_id: 'phone_id',
  to: '+1234567890',
  body: 'Hello World!'
)

# Text with URL preview
client.messages.send_text(
  phone_number_id: 'phone_id',
  to: '+1234567890',
  body: 'Check this out: https://example.com',
  preview_url: true
)

Media Messages

# Send image
client.messages.send_image(
  phone_number_id: 'phone_id',
  to: '+1234567890',
  image: {
    link: 'https://example.com/image.jpg',
    caption: 'Beautiful sunset'
  }
)

# Send document
client.messages.send_document(
  phone_number_id: 'phone_id',
  to: '+1234567890',
  document: {
    id: 'media_id', # or link: 'https://...'
    filename: 'report.pdf',
    caption: 'Monthly report'
  }
)

# Send audio
client.messages.send_audio(
  phone_number_id: 'phone_id',
  to: '+1234567890',
  audio: { id: 'audio_media_id' }
)

# Send video
client.messages.send_video(
  phone_number_id: 'phone_id',
  to: '+1234567890',
  video: {
    link: 'https://example.com/video.mp4',
    caption: 'Tutorial video'
  }
)

Interactive Messages

# Button interactive message
client.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 message
client.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'
        }
      ]
    }
  ]
)

Template Messages

# Simple template
client.messages.send_template(
  phone_number_id: 'phone_id',
  to: '+1234567890',
  name: 'hello_world',
  language: 'en_US'
)

# Template with parameters
client.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 reaction
client.messages.send_reaction(
  phone_number_id: 'phone_id',
  to: '+1234567890',
  message_id: 'message_to_react_to',
  emoji: '👍'
)

# Remove reaction
client.messages.send_reaction(
  phone_number_id: 'phone_id',
  to: '+1234567890',
  message_id: 'message_to_react_to',
  emoji: nil
)

Message Status

# Mark message as read
client.messages.mark_read(
  phone_number_id: 'phone_id',
  message_id: 'message_id'
)

# Send typing indicator
client.messages.send_typing_indicator(
  phone_number_id: 'phone_id',
  to: '+1234567890'
)

Media Management

Handle media uploads, downloads, and management:

# Upload media
upload_response = client.media.upload(
  phone_number_id: 'phone_id',
  type: 'image',
  file: '/path/to/image.jpg'
)

media_id = upload_response.id

# Get media metadata
metadata = client.media.get(media_id: media_id)
puts "File size: #{metadata.file_size} bytes"
puts "MIME type: #{metadata.mime_type}"

# Download media
content = client.media.download(
  media_id: media_id,
  as: :binary
)

# Save media to file
client.media.save_to_file(
  media_id: media_id,
  filepath: '/path/to/save/file.jpg'
)

# Delete media
client.media.delete(media_id: media_id)

Template Management

Create, manage, and use message templates:

# List templates
templates = client.templates.list(
  business_account_id: 'your_business_id',
  status: 'APPROVED'
)

# Create marketing template
template_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 template
auth_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 template
client.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 client
kapso_client = KapsoClientRuby::Client.new(
  kapso_api_key: 'your_kapso_key',
  base_url: 'https://app.kapso.ai/api/meta'
)

# Message history
messages = kapso_client.messages.query(
  phone_number_id: 'phone_id',
  direction: 'inbound',
  since: '2024-01-01T00:00:00Z',
  limit: 50
)

# Conversation management
conversations = kapso_client.conversations.list(
  phone_number_id: 'phone_id',
  status: 'active'
)

conversation = kapso_client.conversations.get(
  conversation_id: conversations.data.first.id
)

# Update conversation status
kapso_client.conversations.update_status(
  conversation_id: conversation.id,
  status: 'archived'
)

# Contact management
contacts = kapso_client.contacts.list(
  phone_number_id: 'phone_id',
  limit: 100
)

# Update contact metadata
kapso_client.contacts.update(
  phone_number_id: 'phone_id',
  wa_id: 'contact_wa_id',
  metadata: {
    tags: ['premium', 'customer'],
    source: 'website'
  }
)

# Search contacts
results = kapso_client.contacts.search(
  phone_number_id: 'phone_id',
  query: 'john',
  search_in: ['profile_name', 'phone_number']
)

Configuration

Global Configuration

KapsoClientRuby.configure do |config|
  config.debug = true
  config.timeout = 60
  config.open_timeout = 10
  config.max_retries = 3
  config.retry_delay = 1.0
end

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 mode
client = KapsoClientRuby::Client.new(
  access_token: 'token',
  debug: true
)

# Custom logger
logger = Logger.new(STDOUT)
logger.level = Logger::DEBUG

client = KapsoClientRuby::Client.new(
  access_token: 'token',
  logger: logger
)

Error Handling

The SDK provides comprehensive error handling with detailed categorization:

begin
  client.messages.send_text(
    phone_number_id: 'phone_id',
    to: 'invalid_number',
    body: 'Test'
  )
rescue KapsoClientRuby::Errors::GraphApiError => e
  puts "Error: #{e.message}"
  puts "Category: #{e.category}"
  puts "HTTP Status: #{e.http_status}"
  puts "Code: #{e.code}"
  
  # Check error type
  case e.category
  when :authorization
    puts "Authentication failed - check your access token"
  when :parameter
    puts "Invalid parameter - check phone number format"
  when :throttling
    puts "Rate limited - wait before retrying"
    if e.retry_hint[:retry_after_ms]
      sleep(e.retry_hint[:retry_after_ms] / 1000.0)
    end
  when :template
    puts "Template error - check template name and parameters"
  when :media
    puts "Media error - check file format and size"
  end
  
  # Check retry recommendations
  case e.retry_hint[:action]
  when :retry
    puts "Safe to retry this request"
  when :retry_after
    puts "Retry after specified delay: #{e.retry_hint[:retry_after_ms]}ms"
  when :do_not_retry
    puts "Do not retry - permanent error"
  when :fix_and_retry
    puts "Fix the request and retry"
  when :refresh_token
    puts "Access token needs to be refreshed"
  end
end

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

def send_with_retry(client, max_retries = 3)
  retries = 0
  
  begin
    client.messages.send_text(
      phone_number_id: 'phone_id',
      to: '+1234567890',
      body: 'Test message'
    )
  rescue KapsoClientRuby::Errors::GraphApiError => e
    retries += 1
    
    case e.retry_hint[:action]
    when :retry
      if retries <= max_retries
        sleep(retries * 2) # Exponential backoff
        retry
      end
    when :retry_after
      if e.retry_hint[:retry_after_ms] && retries <= max_retries
        sleep(e.retry_hint[:retry_after_ms] / 1000.0)
        retry
      end
    end
    
    raise # Re-raise if no retry
  end
end

Webhook Handling

Handle incoming webhooks from WhatsApp:

# Verify webhook signature
def verify_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_sig
end

# In your webhook endpoint
def handle_webhook(request)
  payload = request.body.read
  signature = request.headers['X-Hub-Signature-256']
  
  unless verify_webhook_signature(payload, signature, ENV['WHATSAPP_APP_SECRET'])
    return [401, {}, ['Unauthorized']]
  end
  
  webhook_data = JSON.parse(payload)
  
  # Process webhook data
  webhook_data['entry'].each do |entry|
    entry['changes'].each do |change|
      if change['field'] == 'messages'
        messages = change['value']['messages'] || []
        messages.each do |message|
          handle_incoming_message(message)
        end
      end
    end
  end
  
  [200, {}, ['OK']]
end

def handle_incoming_message(message)
  case message['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']}"
  end
end

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.rb
require 'vcr'

VCR.configure do |config|
  config.cassette_library_dir = 'spec/vcr_cassettes'
  config.hook_into :webmock
  config.configure_rspec_metadata!
  
  # Filter sensitive data
  config.filter_sensitive_data('<ACCESS_TOKEN>') { ENV['WHATSAPP_ACCESS_TOKEN'] }
  config.filter_sensitive_data('<PHONE_NUMBER_ID>') { ENV['PHONE_NUMBER_ID'] }
end

# In your tests
RSpec.describe 'Messages' do
  it 'sends text message', :vcr do
    client = 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).to be_present
  end
end

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