Skip to main content

Prerequisites

Before installing whatsapp-rust, ensure you have:
  • Rust 1.94 or newer — the workspace MSRV, and all default features build on stable Rust. See Using stable Rust.
  • Cargo package manager
SQLite is bundled by default with the whatsapp-rust-sqlite-storage crate, so you don’t need to install it separately. If you prefer to link against a system-installed SQLite, disable the default bundled-sqlite feature.

Add to your project

whatsapp-rust re-exports the entire stack (wacore, wacore_binary, waproto, and all bundled implementations), so one dependency line is enough for most projects:
Cargo.toml
Every sub-crate path is reachable through the main crate:
  • whatsapp_rust::waproto::whatsapp (aliased as wa in prelude)
  • whatsapp_rust::wacore, whatsapp_rust::wacore_binary
  • whatsapp_rust::store::SqliteStore, whatsapp_rust::http::UreqHttpClient, whatsapp_rust::transport::TokioWebSocketTransportFactory
whatsapp-rust also re-exports every third-party crate whose types appear in its public API, so you never need to add or version-pin any of these yourself:
  • whatsapp_rust::buffa — sub-message fields on wa::Message (MessageField; also re-exported directly from prelude) — see waproto
  • whatsapp_rust::anyhow — the error type on the store/transport/InboundDurabilityHook traits
  • whatsapp_rust::async_trait — the macro required to implement those traits
  • whatsapp_rust::bytesTransport::send payloads
  • whatsapp_rust::chrono — timestamps returned by wacore::time and message metadata
  • whatsapp_rust::futures — the oneshot::Receiver returned by response-waiting accessors
  • whatsapp_rust::serde, whatsapp_rust::serde_json, whatsapp_rust::async_channel
This is purely additive — you can still add any of these crates directly (for example to pin your own version, or to use APIs beyond what whatsapp-rust re-exports).
The same holds for git consumers — no need to pin every sibling crate:
Cargo.toml
If you need to declare a sibling crate explicitly (for example to enable a crate-specific feature flag), you can still add it individually. The full multi-crate form:
Cargo.toml

Feature flags

whatsapp-rust supports several optional features:
Breaking change (unreleased): enable passkey explicitly if you use passkey linking — it’s now opt-in and off by default, where earlier versions compiled it into every build. Add features = ["passkey"] before upgrading — see the row above.
Breaking change (unreleased): PR #1364 split the former voip-runtime in two — the portable signaling/facade half kept the name, and the native UDP/DTLS/SCTP relay dialer moved to a new voip-relay-native feature. If your Cargo.toml names voip-mlow, voip-libopus, or voip-encoded directly (rather than the voip aggregate), add voip-relay-native alongside it to keep the built-in relay transport — without it those features still compile, but every call now fails at setup instead. voip itself already pulled in the native dialer and needs no change. See VoIP Calls — Custom relay transport for the alternative: installing your own RelayTransportProvider, which is also what makes voip-mlow/voip-libopus/voip-encoded build on wasm32 now.
All default features enable Tokio as the async runtime, but every component is optional. To target a different runtime (async-std, WASM, etc.), disable all defaults and provide your own implementations of the Runtime, TransportFactory, HttpClient, and Backend traits. See custom backends for details.
The wacore crate has an additional feature for WASM browser targets: To use whatsapp-rust in a WASM browser environment, enable the js feature on wacore:
Cargo.toml
wacore is not the only crate that builds for wasm32 — as of PR #1364, whatsapp-rust itself does too, with voip-mlow, voip-libopus, or voip-encoded (never voip-relay-native, which needs a UDP socket the target doesn’t have; also disable whatsapp-rust’s own defaults with default-features = false — see VoIP Calls — Architecture). Still enable js for random number generation, and install your own RelayTransportProvider — an RTCPeerConnection in a browser — since none of those features link a way onto the media wire on their own.While PR #1364 is unreleased, reaching it means depending on whatsapp-rust via git, as shown above — and js then has to be enabled on the same git-sourced wacore, not the crates.io one in the snippet above: Cargo treats a registry copy and a git copy of the same crate as different packages entirely and won’t unify features across them, so js on the crates.io wacore never reaches the wacore your git-sourced whatsapp-rust actually links.
Cargo.toml
The waproto crate has its own feature flags: build.rs always runs and generates whatsapp.rs into OUT_DIR — you do not need any feature flag for normal builds. All protobuf types derive Serialize by default. Enable serde-deserialize when you need to parse protobuf types from JSON (e.g., in a WASM bridge). Enable serde-snake-case when your JSON source uses snake_case for enum variants (buffa generates SCREAMING_SNAKE_CASE by default).
Cargo.toml
The whatsapp-rust-sqlite-storage crate has its own feature flags: To use a system-installed SQLite instead of the bundled version:
Cargo.toml
whatsapp-rust previously shipped an optional chat/message history store, whatsapp-rust-chat-store. We extracted it into its own repository, since materializing an event stream into chats, previews, and unread counts is an application decision rather than a protocol one. It no longer ships from whatsapp-rust — link to be added here once the replacement repository is published.
The default features provide everything needed for most use cases. Only customize features if you have specific requirements.

Using stable Rust

whatsapp-rust uses Rust edition 2024 and declares an MSRV of 1.94, both of which stable Rust supports, and the default feature set has no nightly-only dependency — cargo build/cargo add whatsapp-rust works with stable Rust out of the box, no feature flags to disable.
The project’s own rust-toolchain.toml still pins a nightly compiler (nightly-2026-06-16), but only for internal, binary-size-focused build flags — -Zshare-generics and lld/ICF linking, set workspace-wide in .cargo/config.toml, plus -Zbuild-std for the Docker image build only (see Docker deployment below) — not for any language feature the published crates need. That pin governs building the whatsapp-rust workspace itself; it has no effect on your project when you depend on whatsapp-rust from crates.io or git.

Performance: codegen flags

whatsapp-rust sets no -C target-feature of its own. A published crate cannot know which CPU it will run on, and the flag has no runtime fallback — a binary built with a feature the CPU lacks doesn’t refuse to start, it traps with SIGILL the first time execution reaches an instruction using it, which can be well after a clean startup and readiness probe. If you control your own deployment target, you can set the flag yourself. On the Signal (libsignal) paths, +bmi2,+avx2 together are worth roughly a fifth of the per-message instruction count+bmi2 speeds up the field arithmetic (mulx), and +avx2 vectorizes the constant-time table lookups used in fixed-base scalar multiplication; the two are additive because they touch disjoint code.
.cargo/config.toml
The [target.*] key must match your actual build target, or it’s silently ignored. x86_64-unknown-linux-gnu above covers a glibc host build; the Alpine-based Docker build on this page compiles against musl, so that image needs [target.x86_64-unknown-linux-musl] (or aarch64-unknown-linux-musl on arm64) instead.
Confirm every deployment CPU reports both features before setting this — by an OS-filtered check such as /proc/cpuinfo, not by the CPU’s age or product name. Some cores sold under the same Atom/Celeron/Pentium or comparable AMD names lack one or both (e.g. Silvermont, Goldmont, Jaguar, Puma), while other parts under the same names have both. A raw CPUID bit isn’t enough for +avx2 either — the OS also needs YMM state enabled, which /proc/cpuinfo already accounts for on Linux.
Cargo takes rustflags from exactly one source (CARGO_ENCODED_RUSTFLAGS, then RUSTFLAGS, then target.<triple>.rustflags, then build.rustflags) — they never merge, so setting RUSTFLAGS for any other reason silently discards this entry. Also, unless you pass an explicit --target, cargo applies target.<triple>.rustflags to build scripts and proc macros too, so the build host needs both features as well as the deployment host.
Do not use -Ctarget-cpu=native as a shortcut: it’s unmeasurable under Valgrind-based profilers (it emits instructions they can’t decode on AVX-512 hosts) and was observed slower than the explicit flag list on real hardware, likely from the frequency cost of touching wide vector units. For wasm32-unknown-unknown, -Ctarget-feature=+simd128 is safe to add if every runtime you deploy to supports the SIMD proposal — check the runtime, not the CPU, since an unsupporting engine rejects a module using v128 at validation time rather than trapping at an instruction.

32-bit target support

whatsapp-rust uses portable-atomic instead of std::sync::atomic for 64-bit atomic operations. This means the library works on 32-bit targets (ARM32, MIPS, RISC-V 32, etc.) where AtomicU64 is not natively available — portable-atomic provides a software fallback automatically. No extra configuration is needed. The portable-atomic dependency is included with the fallback feature enabled by default across all crates (whatsapp-rust, wacore, and whatsapp-rust-sqlite-storage).
If you’re building for a 32-bit embedded target or cross-compiling to armv7-unknown-linux-gnueabihf, whatsapp-rust will compile and run correctly out of the box.

Custom features example

If you want to use only specific features:
Cargo.toml

Verify installation

Create a simple test file to verify the installation:
src/main.rs
Run it with:
If you see “whatsapp-rust installed successfully!”, you’re ready to move on to the Quickstart guide.

Docker deployment

whatsapp-rust ships a pre-built multi-arch image to GHCR and includes a Dockerfile for building your own. The runtime image is built from scratch (only the static binary) and runs unprivileged as uid 65532.

Pre-built image

Pull from GitHub Container Registry — a single manifest resolves to linux/amd64 or linux/arm64 automatically based on the host:
Tagged releases are also available (e.g. ghcr.io/oxidezap/whatsapp-rust:0.7.0). The image is published on every push to main, on v* tags, and can be triggered manually via the Actions tab.

Build from source

The build process:
  1. Uses rust:alpine with cargo-chef (pinned to a fixed release with --locked) for efficient, reproducible dependency caching
  2. Detects the host target triple at build time — docker buildx build --platform linux/arm64 produces native binaries without Dockerfile changes
  3. Enables -Zshare-generics=y (−5.6% .text) and recompiles std with the release profile (-Zbuild-std, −~300 KiB more) so it participates in fat LTO — together these two flags reduce .text by roughly 8%; the full optimization series (#842–#845) achieved 15% total
  4. Caches dependency compilation via cargo chef cook --target in a separate layer for fast rebuilds
  5. Produces a final image from scratch containing only the binary

Run the container

Use a named volume for /data. The container runs as uid 65532 and a named volume inherits that ownership automatically, so the SQLite database stays writable without extra setup:
For pair code authentication, pass the --phone flag:
Host bind mounts (e.g. -v ./data:/data) require the host directory to be owned by uid 65532, otherwise the container cannot write the database. Named volumes don’t have this requirement.

Upgrading from an older image

Images before this release ran as root. If an existing named volume has root-owned files, chown it once before restarting:

Graceful shutdown

The container supports graceful shutdown out of the box. When the signal feature is enabled (it is by default), the bot listens for SIGTERM and Ctrl+C, disconnects cleanly from WhatsApp, and exits. Docker sends SIGTERM on docker stop, so the bot will shut down gracefully without losing session state. Since the image is built from scratch, PID 1 is the binary itself. It handles signals directly — no init system like tini is needed. This matters more than usual for PID 1: the kernel silently drops any signal a PID-1 process has no handler installed for, so an unhandled SIGTERM doesn’t just fall back to the default disposition — it’s dropped entirely, and the process only stops once docker stop’s grace period expires and it’s SIGKILLed. The bundled demo and voip-cli binaries get this via whatsapp_rust::shutdown_signal() — an exported async fn (gated on the signal feature) that resolves on the first of SIGINT or SIGTERM on Unix, or Ctrl+C elsewhere. Both handlers are armed before the future first suspends, so a signal arriving right after the first poll is still delivered. Use it in your own main() in place of a bare tokio::signal::ctrl_c() if you also deploy under Docker/Kubernetes/systemd:
The Dockerfile detects the host target triple at build time via rustc -vV, so docker buildx build --platform linux/arm64 (or any other supported platform) works natively without modifying the Dockerfile. The nightly-only build flags (-Zshare-generics, -Zbuild-std) apply only inside this image — stable consumers and local cargo build invocations are unaffected.

Next steps

Quickstart

Build your first WhatsApp bot in minutes