Skip to main content
WhatsApp bans accounts that send too many messages in a short period. The Baileys provider’s built-in rate limiter protects your bot automatically — it’s enabled by default and sits inside the provider’s outbound send pipeline, so every ctx.reply(), bot.send(), bot.broadcast() and media send passes through it.
Transport-owned pacingThe pacing policy lives on the transport provider, not on the core: each transport may enforce its own limits (Telegram 429s, Discord buckets, Cloud API tiers — surfaced as typed RateLimitedErrors). This page covers the WhatsApp (Baileys) token-bucket limiter you configure when constructing BaileysProvider.

How it works

The limiter uses a token bucket algorithm with two independent buckets:
  • Per-chat — limits how fast you can send to a single conversation.
  • Global — limits your total outbound throughput across all chats.
When a bucket is empty, messages are queued (not dropped) and released as tokens refill; a throttled chat does not hold the others back. A send that waits in the queue longer than maxWait (30 s by default) rejects with RateLimiterTimeout, a RateLimitedError, so a caller never hangs. An optional jitter adds a random human-like delay between sends.

Configuration

Pass rateLimit to the provider constructor (BotConfig.rateLimit is deprecated: the core ignores it and logs a warning):

Disable rate limiting

For tests or high-throughput trusted environments:
New accounts are more sensitiveA freshly registered WhatsApp number gets banned faster than an aged one. Start conservative (3 / 10s per chat, 20 / 60s global) and increase gradually.

Programmatic access

The RateLimiter instance is internal, but you can import the class from the transport package for advanced use:

Short rate limits on ctx sends

Whatever the transport, a ctx send (reply, say, media, in handlers, flows and conversations) that the provider rejects with a RateLimitedError asking for a short wait is retried in place, so a conversation step does not end on a transient 429: by default once, when retryAfterMs is at most 5 s. BotConfig.rateLimitRetry tunes it (retries counts attempts after the first):
A longer wait, other errors and bot.send() are not retried in place: they reject with the provider’s error. bot.broadcast() (its retries option) and the durable outbox have their own back-off.

How it interacts with before('send') hooks

The rate limiter runs after before('send') hooks but before the actual provider send. If a hook aborts the send, the rate limiter is never consulted — aborted sends don’t consume tokens.