Basic example
Add the dependencies — one crate is enough;whatsapp-rust re-exports the entire stack:
Cargo.toml
src/main.rs
Step-by-step breakdown
1
Set up the storage backend
The bot needs persistent storage for session data, keys, and state:This creates a SQLite database file named
whatsapp.db in your current directory. The session will persist across restarts.2
Configure the bot builder
The
Bot::builder() pattern lets you configure all components:With the default cargo features, only the backend is required. The Tokio WebSocket transport, ureq HTTP client, and Tokio runtime are pre-wired. Use
with_transport_factory, with_http_client, and with_runtime to override any of them (for example when targeting WASM or a custom transport).3
Register event handlers
Typed registrars cover the common cases — no Available typed registrars:
match &*event needed:on_message, on_qr_code, on_pair_code, on_connected, on_logged_out. Use on_event or on_event_for as catch-alls for events without a typed registrar. Multiple handlers of any kind accumulate — registering a second one no longer silently replaces the first.on_message delivers a ready MessageContext with ctx.reply(text), ctx.reply_quoting(text), ctx.react(emoji), and ctx.send_message(msg) helpers.4
Build and run the bot
Build the bot and start the event loop:
bot.run().await runs the bot on the current task until it disconnects or logs out. To run in the background instead, use bot.spawn(), which returns a BotHandle with client(), shutdown(), and abort().Responding to messages
Let’s extend the bot to respond to “ping” with “pong” using theon_message helper and ctx.reply:
Key methods
msg.text_content()— Extract text from any message type (conversation, extended text, etc.)ctx.reply(text)— Send a plain-text reply in the same chat without quotingctx.reply_quoting(text)— Send a plain-text reply that quotes the received messagectx.react(emoji)— React to the received messagectx.send_message(msg)— Send an arbitrarywa::Messageto the source chatclient.send_text(jid, text)— Send a plain-text message from aClientreferenceinfo.source.chat— The JID (identifier) of the chat where the message came frominfo.source.sender— The JID of the user who sent the message
wa::Message directly:
MessageBuilderExt helpers:
Authentication methods
QR code pairing (default)
The bot automatically generates QR codes when not authenticated. Display them withon_qr_code:
Pair code (phone number)
Alternatively, link using a phone number and 8-digit code:PairCodeOptions derives companion_platform_id and companion_platform_display from the device’s PlatformType by default (Chrome with Chrome (Linux) for the stock web profile). You can override the wire id when needed:
platform_id accepts the CompanionWebClientType wire enum (single-byte ASCII ids). The display string is always derived — there is no separate platform_display field.Pair code and QR code authentication run concurrently. Whichever method completes first will be used.
Running the bot
1
First run - Authentication
On the first run, the bot will generate a QR code:Scan the QR code with WhatsApp on your phone:
- Open WhatsApp on your phone
- Go to Settings → Linked Devices
- Tap “Link a Device”
- Scan the QR code displayed in your terminal
2
Subsequent runs - Auto-login
After pairing, the session is saved. The bot will automatically reconnect:You should see:
3
Test the bot
Send “ping” to your bot from any WhatsApp chat. It should reply with “pong”!
Demo binary CLI flags
The repository includes a demo bot example (examples/demo.rs) that supports CLI arguments for authentication:
🦀ping with a quoted 🏓 Pong! reply, edits the reply to append the send latency, and supports media ping/pong via CDN reuse.
Using MessageContext
on_message delivers a ready MessageContext, so you no longer need to extract it from Arc<Event> manually. Use typed handler functions for cleaner separation:
MessageContext provides convenience methods including send_message (auto-targets the source chat), reply, reply_quoting, react, build_quote_context, edit_message, and revoke_message.
Media forwarding with CDN reuse
You can also forward media instantly by reusing the original CDN fields — no download or re-upload needed:Constructing a message with a sub-message field (like
image_message below) requires MessageField. whatsapp-rust re-exports it directly from prelude, so use whatsapp_rust::prelude::* is enough — no extra Cargo.toml entry needed. See Installation.Background operation and graceful shutdown
Usespawn() to run the bot in the background while your code continues, and shutdown() for a clean stop:
BotHandle also exposes abort() as an escape hatch (skips the flush). Awaiting the handle resolves to () once the run loop exits.
Prefer
shutdown_signal() over a bare tokio::signal::ctrl_c(): it resolves on SIGTERM as well as SIGINT. Supervisors like docker stop, Kubernetes, and systemd stop a process with SIGTERM first — a Ctrl+C-only wait never sees it, so cleanup is skipped and the process is hard-killed once the stop grace period elapses. Requires the signal feature, on by default.Complete example with logging
Here’s a production-ready example with proper logging, reactions, message editing, and media CDN reuse:src/main.rs
Configuring log targets
whatsapp-rust uses thelog crate with module-specific targets for fine-grained filtering. You can use RUST_LOG to control which components emit log output.
Available log targets
Filtering examples
During shutdown or disconnect, the client automatically downgrades sync errors from
error to debug level to reduce noise. This means you won’t see spurious error logs when the client is intentionally disconnecting.Running with Docker
You can also run the bot using Docker instead of compiling locally:/data directory inside the container. Mount a volume to persist it across restarts. The container shuts down gracefully on docker stop — the bot disconnects cleanly from WhatsApp before exiting. See the installation guide for more details.
Benchmarking
The repository includes a benchmark example atexamples/benchmark.rs that you can use for quick integration-level performance testing. It uses an in-memory backend and supports a custom WebSocket URL via the WHATSAPP_WS_URL environment variable:
The benchmark example requires the
danger-skip-tls-verify feature flag because it’s designed for use with local test servers.bench-integration test suite measures real-world scenarios (connect, send, receive, reconnect) and reports wall-clock time plus heap allocation counts per operation:
wacore crate includes an iai-callgrind benchmark suite that measures instruction counts for the full send/receive pipeline (DM and group messaging with various participant counts), binary protocol encoding, Signal Protocol operations, and reporting token generation:
Next steps
Sending messages
Learn about different message types and how to send them
Media handling
Upload and download images, videos, and documents
Group management
Create and manage WhatsApp groups
Client API reference
Explore all available client methods