Skip to content

Configuration ​

ruby
Whatsapp.configure do |config|
  # Connection
  config.auth_dir         = "./my_session"     # Session persistence directory
  config.bridge_timeout   = 15                 # Seconds to wait for bridge startup
  config.node_command     = "node"             # Path to node binary
  config.auto_reconnect   = true               # Auto-reconnect on disconnect
  config.max_reconnect_attempts = 5
  config.reconnect_interval     = 3            # Seconds between retries

  # HTTP timeouts for bridge requests
  config.http_open_timeout  = 5                # Connection timeout (seconds)
  config.http_read_timeout  = 10               # Read timeout (seconds)
  config.http_write_timeout = 10               # Write timeout (seconds)

  # Safety preset (applies rate limits + anti-ban settings)
  config.safety_preset = :strict               # :strict, :moderate, :manual, :off

  # Webhook signature verification
  config.webhook_secret = ENV["WHATSAPP_WEBHOOK_SECRET"]  # HMAC-SHA256

  # Heartbeat
  config.heartbeat_enabled  = true             # Periodic health checks
  config.heartbeat_interval = 30               # Seconds

  # Message handler (Rails — receives all incoming messages)
  config.message_handler = "WhatsappMessageHandler"

  # Logging
  config.log_level = :info                     # :debug, :info, :warn, :error, :fatal
  config.log_path  = $stdout                   # IO, filepath string, or Logger instance
end

Safety presets ​

Safety presets configure all rate limiting and anti-ban settings at once. The default is :strict.

ruby
config.safety_preset = :strict   # conservative — recommended for new accounts
config.safety_preset = :moderate # relaxed — for established accounts
config.safety_preset = :manual   # no changes — set everything manually
config.safety_preset = :off      # no limits — dev/testing only!
Setting:strict:moderate:off
Messages/min610unlimited
Messages/hour4080unlimited
Messages/day150300unlimited
Per recipient/hour510unlimited
New chats/day1020unlimited
Min delay between msgs3.0s2.0s0s
Delay jitter (gaussian)5.0s3.0s0s
Post-connect delay60s30s0s
Typing simulationyesnono
Auto read receiptsyesnono
Ramp-up after idleyesyesno
Account warmup (days)730

You can override individual settings after applying a preset:

ruby
config.safety_preset = :strict
config.max_new_chats_per_day = 15  # override just this

All safety settings ​

ruby
Whatsapp.configure do |config|
  config.safety_preset = :manual

  # Rate limits
  config.rate_limiting_enabled           = true
  config.rate_limit_per_minute           = 8
  config.rate_limit_per_hour             = 60
  config.rate_limit_per_day              = 200
  config.rate_limit_per_recipient_per_hour = 5
  config.min_delay_between_messages      = 3.0  # seconds
  config.delay_jitter                    = 5.0  # max gaussian jitter

  # Anti-ban
  config.max_new_chats_per_day = 10             # new conversations per day
  config.post_connect_delay    = 60.0           # wait before first message
  config.ramp_up_enabled       = true           # warmup after idle periods
  config.idle_threshold        = 1800.0         # idle threshold (seconds)
  config.warmup_messages       = 5              # messages to ramp up over
  config.warmup_delay_factor   = 3.0            # extra delay multiplier during warmup

  # Behavior simulation
  config.typing_simulation     = true           # auto "composing" before text messages
  config.auto_read_receipts    = true           # auto read receipts for incoming
  config.auto_presence_updates = false          # auto available/unavailable on connect

  # Progressive account warmup
  config.ramp_up_days       = 7                 # day 1 = 1/7 limits, day 7 = full
  config.ramp_up_started_at = nil               # auto-set on first message
end

Progressive account warmup ​

New accounts start with reduced limits that increase daily over ramp_up_days:

  • Day 1: 1/7 of all limits (~21 msgs/day, ~1 new chat)
  • Day 4: 4/7 of all limits (~85 msgs/day, ~6 new chats)
  • Day 7+: full limits (150 msgs/day, 10 new chats)

ramp_up_started_at is auto-set when the first message is sent. To reset the warmup:

ruby
Whatsapp.configuration.ramp_up_started_at = Time.now

Safety monitoring ​

ruby
client.safety_status
# => {
#   risk: :low,              # :low, :medium, :high
#   preset: :strict,
#   warmup_day: 3,
#   warmup_factor: 0.43,
#   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 ​

For a single urgent message, skip all protections:

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

WARNING

unsafe: true bypasses rate limiting, typing simulation, and all anti-ban protections. Use only when absolutely necessary.

Rate limiting ​

Two layers of protection ​

  1. Ruby side (Whatsapp::RateLimiter) — primary control with multi-level limits, gaussian jitter, warmup, and new chat tracking.
  2. Bridge side (Node.js) — last line of defense, returns HTTP 429 if limits are exceeded.

Handling rate limits ​

ruby
begin
  client.send_message(to: "+33...", text: "hello")
rescue Whatsapp::RateLimited => e
  e.period     # :minute, :hour, :day
  e.limit      # 6
  e.current    # 6
  e.recipient  # nil (global) or "+33..." (per-recipient)
end

Monitoring ​

ruby
client.rate_limit_stats
# => { last_minute: 3, last_hour: 25, last_day: 102, ..., new_chats_today: 4, warmup_day: 3 }

Health check ​

A background heartbeat thread pings the bridge and WhatsApp connection periodically. Starts on connect, stops on disconnect.

ruby
session.heartbeat.check_now
session.heartbeat.healthy?             # => true/false
session.heartbeat.last_status          # => :ok, :bridge_down, :whatsapp_disconnected, ...
session.heartbeat.consecutive_failures # => 0

# Via AccountProxy
hotel.whatsapp.healthy?
hotel.whatsapp.health

Health events ​

ruby
session.on('health.ok')        { |data| ... }
session.on('health.failure')   { |data| ... }
session.on('health.recovered') { |data| ... }

Failure reasons: :bridge_down, :bridge_not_initialized, :bridge_unhealthy, :whatsapp_disconnected, :error.

Security ​

HTTP timeouts ​

All bridge HTTP requests have configurable timeouts to prevent hanging connections:

ruby
Whatsapp.configure do |config|
  config.http_open_timeout  = 5   # default: 5s
  config.http_read_timeout  = 10  # default: 10s
  config.http_write_timeout = 10  # default: 10s
end

Webhook signature verification ​

Verify the authenticity of incoming webhook payloads with HMAC-SHA256:

ruby
Whatsapp.configure do |config|
  config.webhook_secret = ENV["WHATSAPP_WEBHOOK_SECRET"]
end

Verify manually in your controller:

ruby
payload = request.raw_post
signature = request.headers["X-Whatsapp-Signature"]
Whatsapp::WebhookSignature.verify!(payload, signature, Whatsapp.configuration.webhook_secret)

Methods available:

MethodDescription
WebhookSignature.sign(payload, secret)Generate a sha256=... signature
WebhookSignature.valid?(payload, sig, secret)Returns true/false
WebhookSignature.verify!(payload, sig, secret)Raises WebhookSignatureError on failure

Session file permissions ​

Auth session files are automatically secured:

  • Auth directory: created with 0700 permissions (owner-only access)
  • Auth files (creds, keys): set to 0600 after each credential update
  • Both Ruby and Node.js sides enforce these permissions

Path traversal protection ​

Session names are validated to prevent directory traversal attacks:

ruby
Whatsapp.session("../../etc/passwd")
# => ArgumentError: Invalid session name "../../etc/passwd": must not contain .., / or \

Valid names: alphanumeric, hyphens, underscores (e.g. hotel_42, my-session).

Core dump protection ​

Core dumps are disabled at bridge startup to prevent session credentials from leaking to disk:

ruby
Process.setrlimit(Process::RLIMIT_CORE, 0, 0)

Log secret filtering ​

Sensitive keys are automatically redacted in log output:

ruby
logger.info "config", webhook_secret: "my_key"
# => [whatsapp-ruby] [14:30:00] config webhook_secret="[FILTERED]"

Filtered keys: secret, token, api_key, password, credentials, creds, webhook_secret, auth_token, access_token, refresh_token.

Other built-in protections ​

ProtectionDetails
No evalNo dynamic code execution from user input
No shell interpolationAll process spawning uses array form (Process.spawn, system)
Localhost bindingBridge listens on 127.0.0.1 only, never 0.0.0.0
Auth folder isolationAuth directory never served via HTTP, never exposed in error responses
Input sanitizationPhone numbers stripped to digits only via gsub(/[^\d]/, "")

MIT License