whatsapp-ruby

Ruby client for WhatsApp via a local Node.js bridge powered by Baileys.

Send and receive WhatsApp messages from Ruby. Includes Rails integration with multi-session support.

Disclaimer: This project uses WhatsApp Web's unofficial API via Baileys. It is not affiliated with or endorsed by WhatsApp/Meta. Use at your own risk — your account may be banned. Do not use for spam.

Warnings

  • Account ban risk — WhatsApp detects and bans accounts using unofficial APIs. Use a secondary number.
  • Not for bulk messaging — Designed for low-volume messaging (support, notifications). For bulk, use the official WhatsApp Business API.
  • Terms of Service — Using this library violates WhatsApp's ToS. You are solely responsible.

Requirements

  • Ruby >= 3.1
  • Node.js >= 18

Installation

gem 'whatsapp-ruby'

Note: require 'whatsapp' is handled automatically by Bundler. If you use the gem without Bundler, use require 'whatsapp'.

Node dependencies are installed automatically on first connection.

Quick start

require 'whatsapp'

client = Whatsapp.client

client.on_qr { |qr| puts qr }
client.on_connected { puts "Connected!" }
client.on_message { |msg| puts "#{msg.from}: #{msg.text}" }

client.connect

client.send_message(to: "+33612345678", text: "Hello from Ruby!")
sleep

Rails — destination mode

One global account sends to your users' phone numbers.

class User < ApplicationRecord
  has_whatsapp :phone
end

user.whatsapp.send_message("Your code is 1234")
user.whatsapp.send_image("receipt.jpg", caption: "Your invoice")
user.whatsapp.send_location(latitude: 48.8566, longitude: 2.3522, name: "Paris")
user.whatsapp.react(message_id: "msg123", emoji: "👍")
user.whatsapp.reply(quoted_message_id: "msg123", text: "Got it!")
user.whatsapp.send_poll(question: "Satisfied?", options: ["Yes", "No"])
user.whatsapp.send_composing           # typing indicator
user.whatsapp.mark_as_read("msg123")   # read receipt

Rails — account mode (multi-session)

Each record is its own WhatsApp account. Perfect for SaaS apps.

class Hotel < ApplicationRecord
  has_whatsapp :phone, account: true
end

hotel.whatsapp.connect         # starts WhatsApp for this hotel
hotel.whatsapp.qr_data         # QR code to scan
hotel.whatsapp.send_message(to: "+33612345678", text: "Bienvenue!")
hotel.whatsapp.on_message { |msg| puts msg.text }
hotel.whatsapp.on_poll_vote { |data| puts data[:votes] }
hotel.whatsapp.healthy?        # heartbeat status

Generators

rails generate whatsapp:install   # initializer + SendJob
rails generate whatsapp:webhook   # message handler + sessions controller + routes

Sending messages

Method Description
send_message(text) Text message
send_image(file, caption:) Image
send_document(file, filename:) Document
send_video(file, caption:) Video
send_audio(file) Audio
send_sticker(file) Sticker
send_location(latitude:, longitude:, name:) Location
send_contact(name:, phone:) Contact card (vCard)
react(message_id:, emoji:) Reaction
reply(quoted_message_id:, text:) Quoted reply
send_poll(question:, options:, selectable_count:) Poll (single/multi-choice)

Polls

# Single-choice poll
client.send_poll(
  to: "+33612345678",
  question: "Ready for check-in?",
  options: ["Yes", "No", "Later"],
  selectable_count: 1
)

# Multi-choice poll (0 = unlimited selections)
client.send_poll(
  to: "+33612345678",
  question: "Which features do you want?",
  options: ["Pool", "Spa", "Restaurant", "Gym"],
  selectable_count: 0
)

# Receive votes (decrypted automatically)
client.on_poll_vote do |data|
  puts "Poll #{data[:poll_message_id]}: #{data[:votes]}"
  # votes = [{ name: "Yes", voters: ["33612345678@s.whatsapp.net"] }, ...]
end

Contacts

client.on_whatsapp?("+33612345678")                   # => { exists: true, jid: "336..." }
client.profile_picture("+33612345678")                 # => { url: "https://..." }
client.profile_picture("+33612345678", high_res: true) # full resolution
client.fetch_status("+33612345678")                    # => { status: "Hey there!", set_at: ... }
client.business_profile("+33612345678")                # => { profile: { description: ... } }

client.on_contacts_update { |data| puts data }

Presence & read receipts

# Typing indicators
client.send_presence("composing", to: "+33612345678")
client.send_presence("paused", to: "+33612345678")

# Subscribe to contact's presence
client.subscribe_presence("+33612345678")
client.on_presence_update { |data| puts data }

# Mark messages as read
client.read_messages([{ remote_jid: "33612345678@s.whatsapp.net", id: "msg123" }])
client.on_message_receipt { |data| puts data }

Anti-ban safety

Built-in protections to reduce ban risk. Enabled by default with the :strict preset.

Safety presets

Whatsapp.configure do |config|
  config.safety_preset = :strict   # conservative (default)
  config.safety_preset = :moderate # more relaxed
  config.safety_preset = :off      # no limits (dev/testing only!)
  config.safety_preset = :manual   # full manual control
end
Setting :strict :moderate :off
Messages/min 6 10 unlimited
Messages/hour 40 80 unlimited
Messages/day 150 300 unlimited
New chats/day 10 20 unlimited
Post-connect delay 60s 30s 0s
Typing simulation yes no no
Read receipts yes no no
Ramp-up after idle yes yes no
Account warmup (days) 7 3 0

Progressive account warmup

New accounts start with reduced limits that increase daily:

Whatsapp.configure do |config|
  config.ramp_up_days = 7  # day 1 = 1/7 of limits, day 7 = full
end

Day 1: ~21 msgs/day, ~1 new chat. Day 4: ~85 msgs/day, ~6 new chats. Day 7+: full limits.

Safety monitoring

client.safety_status
# => {
#   risk: :low,              # :low, :medium, :high
#   preset: :strict,
#   warmup_day: 3,           # current warmup day
#   warmup_factor: 0.43,     # limits multiplier
#   limits: {
#     per_day:    { used: 12, limit: 64, usage_pct: 18.8 },
#     per_hour:   { used: 5,  limit: 17, usage_pct: 29.4 },
#     new_chats:  { used: 2,  limit: 4,  usage_pct: 50.0 }
#   },
#   features: {
#     typing_simulation: true,
#     auto_read_receipts: true,
#     ramp_up_enabled: true,
#     post_connect_delay: 60.0
#   }
# }

Bypass safety (use with caution)

client.send_message(to: "+33612345678", text: "Urgent", unsafe: true)

Skips rate limiting, typing simulation, and all anti-ban protections for that single message.

Security

  • HTTP timeouts — All bridge requests have configurable timeouts (5s open, 10s read/write)
  • Webhook signature — HMAC-SHA256 verification via Whatsapp::WebhookSignature
  • Session file permissions — Auth dir 0700, auth files 0600 (enforced by Ruby + Node)
  • Path traversal protection — Session names validated against ../ attacks
  • Log secret filtering — Sensitive keys automatically redacted ([FILTERED])
  • Core dump disabled — Prevents credential leaks via crash dumps
  • Localhost binding — Bridge listens on 127.0.0.1 only
  • No eval / no shell interpolation — All process spawning uses safe array form
Whatsapp.configure do |config|
  config.webhook_secret = ENV["WHATSAPP_WEBHOOK_SECRET"]
  config.http_open_timeout  = 5
  config.http_read_timeout  = 10
  config.http_write_timeout = 10
end

See Configuration > Security for details.

Groups

client.groups                                                        # list all groups
client.group("120363001234567890@g.us")                              # group details
client.create_group(subject: "Team", participants: ["+336..."])      # create
client.update_group("g@g.us", subject: "New Name")                  # update
client.add_group_participants("g@g.us", participants: ["+336..."])   # add members
client.remove_group_participants("g@g.us", participants: ["+336..."]) # remove members

Receiving messages

client.on_message do |msg|
  msg.text              # Text content
  msg.type              # :text, :image, :video, :location, :contact, ...
  msg.from_me?          # Sent by you?
  msg.media?            # Image/video/audio/document/sticker?
  msg.location          # { latitude:, longitude:, name:, address: }
  msg.contact_info      # { display_name:, vcard: }
  msg.reply?            # Is a reply?
  msg.quoted_message_id # Quoted message ID
end

Documentation

Full documentation: docs.whatsapp-ruby.dev (or GitHub Pages mirror)

Guide Content
Getting Started Installation, quick start
Configuration All config options, rate limiting, health check
Rails Integration Model macro, multi-session, view helpers, webhook, notifier
Events & Messages Events, message object, error handling
Architecture How it works, design decisions, class overview

Development

git clone https://github.com/seryl/whatsapp-ruby
cd whatsapp-ruby
bundle install
cd bridge && npm install && cd ..
bundle exec rspec

Interactive test: bundle exec ruby test_connect.rb

License

MIT