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, userequire '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. { |msg| puts "#{msg.from}: #{msg.text}" }
client.connect
client.(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.("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.(to: "+33612345678", text: "Bienvenue!")
hotel.whatsapp. { |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.([{ remote_jid: "33612345678@s.whatsapp.net", id: "msg123" }])
client. { |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.(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 files0600(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.1only - 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. 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
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