Configuration

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.

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/min 6 10 unlimited
Messages/hour 40 80 unlimited
Messages/day 150 300 unlimited
Per recipient/hour 5 10 unlimited
New chats/day 10 20 unlimited
Min delay between msgs 3.0s 2.0s 0s
Delay jitter (gaussian) 5.0s 3.0s 0s
Post-connect delay 60s 30s 0s
Typing simulation yes no no
Auto read receipts yes no no
Ramp-up after idle yes yes no
Account warmup (days) 7 3 0

You can override individual settings after applying a preset:

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

All safety settings

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:

Whatsapp.configuration.ramp_up_started_at = Time.now

Safety monitoring

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:

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

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

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

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.

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

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:

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:

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

Verify manually in your controller:

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

Methods available:

Method Description
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:

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:

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

Log secret filtering

Sensitive keys are automatically redacted in log output:

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

Protection Details
No eval No dynamic code execution from user input
No shell interpolation All process spawning uses array form (Process.spawn, system)
Localhost binding Bridge listens on 127.0.0.1 only, never 0.0.0.0
Auth folder isolation Auth directory never served via HTTP, never exposed in error responses
Input sanitization Phone numbers stripped to digits only via gsub(/[^\d]/, "")