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
 = client.media.get(media_id: media_id)
puts "File size: #{.file_size} bytes"
puts "MIME type: #{.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