Overview
whatsapp-rust supports end-to-end encrypted 1:1 voice calls that interoperate with the official WhatsApp app. The full media path is implemented in pure Rust: mic capture, encoding, E2E-SRTP encryption, relay transport, decryption, decoding, and playout.Voice calling is behind the optional
voip feature flag. The default build is entirely unaffected — none of the codec or relay dependencies are compiled unless you opt in.Enabling the Feature
voip is published, you can switch to the version form:
Answering an Incoming Call
Event::IncomingCall fires for all call-control stanzas (offer, preaccept, accept, reject, terminate, transport, relaylatency). Guard on CallAction::Offer before calling accept() — otherwise non-offer events return CallError::NotAnOffer:
accept(...).start() drives the media plane only (callKey decrypt → relay connect → engine). Sending <preaccept> and <accept> signaling stanzas to the peer is the caller’s responsibility and must happen before or alongside start(). See examples/voip-cli/src/main.rs in the repository for a complete incoming-call handler that includes signaling.Placing an Outgoing Call
If the callee has a stored trusted-contact token, it’s attached to the offer automatically, and a fresh token is issued to them in the background afterward if the sender-side bucket has rolled over — matching WhatsApp Web’s
sendTcToken in StartCall.js. This prevents 463 nacks on calls to privacy-restricted contacts and needs no action from the caller. Group-call initiation doesn’t implement this yet.Audio I/O
You supply the audio I/O by implementing theAudioSource and AudioSink traits. Both traits are channel-based — the library reads from a Receiver and writes decoded PCM to a Sender. The bundled examples/voip-cli/src/main.rs wires up cpal/PipeWire as a reference.
The CLI exposes three subcommands:
To test the audio stack locally without a WhatsApp session:
AudioSource / AudioSink implementation from the example:
Call Handle
start() returns a CallHandle for controlling an active call:
Multi-Device Behavior
The library handles multi-device call scenarios automatically:- Companion answering — if another linked device picks up, the library performs a recv-key rekey to that device.
- Sibling dismiss — if a sibling device declines or answers elsewhere, the call tears down cleanly on this device.
- Offline missed calls — missed-call surfacing for devices that were offline when the call arrived.
Architecture: CallEngine
The core CallEngine lives in wacore and is sans-IO — it owns no socket, clock, or thread. You feed it relay packets, mic frames, and timer ticks; it emits transmit packets, playout PCM, call events, and the next deadline.
MLow Codec
WhatsApp’s voice codec (“MLow”) is a heavily modified Opus variant. whatsapp-rust includes a pure Rust port — no FFI, no C library. It compiles to WASM and embedded targets alongside the rest of the crate and is pinned by a byte-exact golden roundtrip test. The decoder operates at a single fixed point:Encryption
Call audio is end-to-end encrypted the WhatsApp way:- The call key arrives over the peer’s Signal session.
- E2E-SRTP keys are derived with HKDF + the libsrtp AES-CM KDF.
- Audio frames are protected with AES-128-CTR and authenticated with a 4-byte WARP MESSAGE-INTEGRITY tag (HMAC-SHA1).
- The SFrame layer wraps the SRTP payload.
Validation
The implementation is tested at multiple levels:- Byte-exact golden roundtrip for the MLow codec
- Known-answer-test vectors for the E2E-SRTP crypto
- In-tree loopback DTLS/SCTP transport E2E test
- Live tested end-to-end against the real WhatsApp app over 3G/WiFi/5G
Roadmap
The 1:1 audio path is the foundation. Natural follow-ups tracked upstream:- Group calls — the signaling and SFrame/SRTP key management generalize to multi-party; the engine is already participant-aware.
- Video calls — the DTLS/SCTP/SRTP transport is codec-agnostic; video is a second media stream.
- Deeper codec coverage — inband FEC, PLC, CNG, and low-bitrate operating points.
- Embedded demo — the sans-IO core already builds for esp32.
Next Steps
- Installation — enable the
voipfeature flag - Advanced: Signal Protocol — how call keys are derived from Signal sessions