Skip to main content
This guide will help you create a simple WhatsApp bot that responds to messages. You’ll learn the core concepts and have a working bot by the end.

Basic example

Add the dependencies — one crate is enough; whatsapp-rust re-exports the entire stack:
Cargo.toml
Here’s a minimal bot that responds to “ping” messages:
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 match &*event needed:
Available typed registrars: 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 the on_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 quoting
  • ctx.reply_quoting(text) — Send a plain-text reply that quotes the received message
  • ctx.react(emoji) — React to the received message
  • ctx.send_message(msg) — Send an arbitrary wa::Message to the source chat
  • client.send_text(jid, text) — Send a plain-text message from a Client reference
  • info.source.chat — The JID (identifier) of the chat where the message came from
  • info.source.sender — The JID of the user who sent the message
For raw protobuf access (advanced cases), build a wa::Message directly:
Or with the MessageBuilderExt helpers:

Authentication methods

QR code pairing (default)

The bot automatically generates QR codes when not authenticated. Display them with on_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:
  1. Open WhatsApp on your phone
  2. Go to Settings → Linked Devices
  3. Tap “Link a Device”
  4. 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:
The demo bot responds to 🦀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:
Then define focused handler functions:
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.
See the media forwarding guide for more details.

Background operation and graceful shutdown

Use spawn() 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 the log 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:
Session data is stored in the /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 at examples/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.
For more comprehensive integration benchmarks with allocation tracking, the bench-integration test suite measures real-world scenarios (connect, send, receive, reconnect) and reports wall-clock time plus heap allocation counts per operation:
For low-level protocol benchmarks, the 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:
See the wacore benchmarks documentation for details on each suite, allocation optimizations, and CI integration.

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