💎 ChatSDK Ruby

WhatsApp Adapter

WhatsApp Business Cloud API adapter with HMAC-SHA256 signature verification and interactive message rendering via the chat_sdk-whatsapp gem.

The WhatsApp adapter (chat_sdk-whatsapp) integrates with the WhatsApp Business Cloud API with HMAC-SHA256 webhook signature verification, interactive button messages, and emoji reactions.

Installation

# Gemfile
gem "chat_sdk"
gem "chat_sdk-whatsapp"

Configuration

require "chat_sdk"
require "chat_sdk/whatsapp"

whatsapp = ChatSDK::WhatsApp::Adapter.new(
  access_token: ENV["WHATSAPP_ACCESS_TOKEN"],       # Permanent or temporary access token
  app_secret: ENV["WHATSAPP_APP_SECRET"],            # Facebook App Secret
  phone_number_id: ENV["WHATSAPP_PHONE_NUMBER_ID"],  # WhatsApp Phone Number ID
  verify_token: ENV["WHATSAPP_VERIFY_TOKEN"]         # Webhook verify token
)

access_token and phone_number_id are required. app_secret is needed for webhook signature verification. verify_token is needed for the webhook verification handshake.

Environment Variables

VariableDescription
WHATSAPP_ACCESS_TOKENPermanent or temporary access token from Meta Developer Console
WHATSAPP_APP_SECRETYour Facebook App Secret (for webhook signature verification)
WHATSAPP_PHONE_NUMBER_IDPhone Number ID from WhatsApp Business settings
WHATSAPP_VERIFY_TOKENCustom token for webhook subscription verification

WhatsApp Business Setup

  1. Create a Meta App in the Developer Console.
  2. Add the WhatsApp product to your app.
  3. In WhatsApp > Getting Started, note your Phone Number ID and generate a temporary access token (or set up a System User for a permanent token).
  4. In WhatsApp > Configuration, set your webhook callback URL and verify token.
  5. Subscribe to the messages webhook field.
  6. Copy your App Secret from Settings > Basic.

Webhook URL

https://your-domain.com/webhooks/whatsapp
map "/webhooks/whatsapp" do
  run bot.webhooks[:whatsapp]
end

The adapter handles both the GET verification handshake (returns hub.challenge) and POST message webhooks.

Capabilities

CapabilitySupportedNotes
direct_messagesYesWhatsApp is inherently 1:1 DM via phone number
file_uploadsYesUpload media then send with media ID
reactionsYesEmoji reactions on messages
edit_messagesNoRaises NotSupportedError
delete_messagesNoRaises NotSupportedError
ephemeral_messagesNoRaises NotSupportedError
typing_indicatorNoRaises NotSupportedError
modalsNoRaises NotSupportedError
streaming_editNoRaises NotSupportedError
threadsNoRaises NotSupportedError
message_historyNoRaises NotSupportedError

Webhook Verification

The adapter verifies incoming POST webhooks using Meta's HMAC-SHA256 signature validation. The X-Hub-Signature-256 header is compared against a computed signature using your App Secret and the raw request body.

For GET requests, the adapter handles the webhook subscription handshake by verifying hub.verify_token and returning hub.challenge.

Interactive Messages

Cards are rendered as WhatsApp interactive messages:

  • Button Message: When the card has a title and up to 3 buttons. Supports header, body text, and reply buttons.
  • Fallback: Plain text for cards that exceed 3 buttons or have only link buttons (WhatsApp reply buttons cannot contain URLs).

Reply button titles are truncated to 20 characters per WhatsApp's limit.

Reactions

Send and remove emoji reactions on messages:

# Add a reaction
whatsapp_adapter.add_reaction(
  channel_id: "15551234567",
  message_id: "wamid.abc123",
  emoji: "\u{1F44D}"
)

# Remove a reaction (empty emoji)
whatsapp_adapter.remove_reaction(
  channel_id: "15551234567",
  message_id: "wamid.abc123",
  emoji: "\u{1F44D}"
)

Direct Client Access

whatsapp_adapter = bot.adapter(:whatsapp)
client = whatsapp_adapter.client  # ApiClient

Mention Formatting

WhatsApp has no mention syntax. mention(user_id) returns the phone number as-is:

whatsapp_adapter.mention("+15551234567")  # => "+15551234567"

Template Messages

Send pre-approved WhatsApp message templates (required for initiating conversations outside the 24-hour window):

# Send a simple template
whatsapp.post_template(
  channel_id: "15551234567",
  template_name: "order_confirmation",
  language_code: "en"
)

# Build template components with the helper
components = ChatSDK::WhatsApp::Adapter.template_components(
  header: "Order #1234",
  body_params: ["Alice", "$49.99"],
  button_payloads: ["track_order"]
)

whatsapp.post_template(
  channel_id: "15551234567",
  template_name: "order_confirmation",
  language_code: "en",
  components: components
)

The template_components class method builds the components array for header text, body parameter substitutions, and quick-reply button payloads.

Mark as Read

Mark an incoming message as read (shows blue check marks to the sender):

whatsapp.mark_as_read(message_id: "wamid.abc123")

Auto-Chunking

Messages longer than 4,096 characters are automatically split into multiple messages. The adapter splits at paragraph boundaries (\n\n), then line boundaries (\n), falling back to the character limit. Each chunk is sent as a separate message in order.

Download Media

Download media (images, documents, audio, video) from WhatsApp by media ID:

binary = whatsapp.download_media(media_id: "media_abc123")
File.write("photo.jpg", binary)

Returns the raw binary content, or nil if the media URL is not available.

On this page