github oxidezap/whatsapp-rust v0.6.0

latest release: v0.7.0
6 months ago

This is the largest release since the project's first crates.io publish. It brings the receive and send pipelines into WhatsApp Web behavioural parity, lands a major performance overhaul (zero-copy node decoding, take-ownership session cache, allocation diet across every hot path), adds first-class support for albums, sticker packs, ephemeral chats, status reactions, message-edit decryption and group call detection, and exposes new extension points for HTTP, transport, and crypto.

Many APIs changed — see Breaking changes below — but the new shape eliminates a long tail of clones, redundant decodes, and stringly-typed parameters that were holding back further optimisation. Downstream crates will need a one-shot migration; in exchange the receive pipeline performs effectively zero deep clones on dispatch.

Highlights

  • Message edits via secret_encrypted_message (#618) — WhatsApp's new E2E envelope for edits (secretEncType = MESSAGE_EDIT) is now decryptable. Ships MessageEdits::decrypt and a generalised secret_enc_addon helper covering the full addon family (PollVote, PollEdit, PollAddOption, EventResponse, EventEdit, EncReaction, EncComment, ReportToken, MessageEdit).
  • Album sends (#449) and sticker pack sends (#454).
  • Ephemeral messages (#450) — send and receive of disappearing chats.
  • Status reactions (#541, #563, #568, #569).
  • Pin / unpin (#424), Client::logout() (#422), per-message message_id in SendOptions (#423).
  • Incoming call events + group call detection (#562, #599).
  • ClientProfile for noise-handshake identity (#596) and Noise_IK + XXfallback (#598).
  • Pluggable SignalCryptoProvider (#561) — bring your own AES-CBC/GCM + HMAC-SHA256 backend.
  • Proxy + custom TLS extension points (#536) and a generic from_websocket transport API (#486).
  • Public refresh_prekeys() (#538) for device-migration flows.
  • LID + PN support in is_on_whatsapp (#453).
  • Lazy history sync blob (#533) — history sync events expose a LazyHistorySync you decode on demand.
  • Full GroupMetadata returned by create_group / community.create (#615).
  • MEX notification dispatcher + consolidated doc-id registry (#574).
  • 32-bit target support via portable-atomic (#481).

Performance

This release is built around a sustained allocation-discipline pass on the receive, send, and signal paths. Selected wins:

  • Receive pipeline: zero-copy node decoding via yoke (#513), NodeStr replacing Cow<str> for inline decoded strings (#514), Arc<wa::Message> end-to-end on dispatch (#613), and Arc<MessageInfo> across retry/PDO (#520). Zero-copy receive pipeline + take-ownership session cache (#522).
  • Startup: −21% allocations, −31% peak heap (#525).
  • Connect time: −23% total allocs, −43% connect-time allocs (#551).
  • Hot-path audits: 11 trims from a targeted pass (#570), per-device sender-key tracking cache (#445), parallel group encrypt fan-out (#610).
  • Token lookup: PHF + SipHash replaced with hashify PTHash (FNV-1a) (#544); single/double-byte token maps unified into one PHF lookup (#543).
  • Jid / nodes: CompactString for Jid.user, NodeValue, NodeContent (#512); zero-copy Serialize for the NodeRef family (#539); zero-alloc dedup (#500); eliminated fmt::Write dispatch from JID formatting (#540).
  • Cache miss penalty: unified device resolution removed a ~700 ms latency spike on cold lookups (#428).
  • Signal store: lightweight PN, cache-first identity, reusable flush buffer (#554); Bytes for sessions, [u8; 32] for identities, pre-encoded acks (#555).
  • Decrypt hot path: LID-PN persist + migrations off-loaded from the decrypt path (#578).

WhatsApp Web compliance

A major theme of 0.6.0. The wire format, addressing, retry, and key-management code paths now match WA Web's reference behaviour:

  • Pair + ADV — companion_platform_id derivation, ADV HMAC enforcement, <prop> round-trip, Android → Chrome mapping (#592, #593, #594, #595, #601).
  • DM multi-device fanout with phash validation (#524); single-device DM retry with chat/requester separation + alternate key lookup (#549, #550).
  • Group / status session rebuild on retry (#559).
  • SKDM — always distribute on first group send, keep tracker on identity change, close forward-secrecy gaps (#603, #604).
  • LID / PN / Hosted addressing — Signal address for Hosted JIDs (#605), unified get_lid_pn_entry(&Jid) (#487), session migration via signal cache (#482), status@broadcast resolves to @lid (#609).
  • Identity change handler completed to match WA Web (#489, #490).
  • PDO recovery + UndecryptableMessage dispatch (#585), <unavailable> via PDO (#506).
  • Stale device filtering via decoded key-index-list (#469); sender-key cache invalidation on device changes (#489).
  • view_once nested-wrapper detection (#602).
  • Block IQ includes LID + pn_jid (#600).
  • Proto bumped to WhatsApp Web 2.3000.1035617621 (#436).

Breaking changes

API ergonomics

  • &[Jid] slices replace Vec<Jid> across feature APIs (#457).
  • #[non_exhaustive] added to event and option enums (#457) — new variants no longer break downstream matches.
  • Typed errors preserve sources across the workspace (#597) — anyhow::Error returns replaced by thiserror enums with #[source], so callers can downcast.
  • Stringly-typed parameters → enums for privacy settings (#438) and several other APIs (#440).
  • Enum JSON discriminators aligned with wire tags via StringEnum (#566); wire-tag derives unified under #[derive(WireEnum)] (#567).

Sender key & device registry

  • Unified per-device sender-key tracking through the device registry (#444).
  • key-index-list decoded to filter stale devices (#469).
  • server_has_prekeys flag persisted to match WA Web (#492).
  • Immutable load_sender_key and auto-reserve marshal (#503).

Device / pair

  • DevicePropsOverride builder; DEVICE_PROPS aligned with WA Web (#586).
  • companion_platform_{id,display} now derived from DeviceProps (#592, #593).
  • PlatformType::Android maps to Chrome; PlatformType::Unknown removed (#601).

Newsletter

  • Sends unified through Client::send_message (#447); newsletter meta node included on every send (#448).

Event bus

  • Arc<Event> event bus eliminates deep clones on dispatch (#515).
  • Arc<wa::Message> end-to-end (#613); Arc<MessageInfo> across message/retry/PDO (#520).

History sync

  • Events replaced with LazyHistorySync (#533) — decode on demand instead of eagerly.

Other

  • LID + PN supported in is_on_whatsapp (#453).
  • Sticker pack APIs (#454).
  • Allocation refactor in upload/send/Jid paths (#471).
  • Comprehensive group feature improvements (#527).
  • Unified LID-PN lookup via get_lid_pn_entry(&Jid) (#487).
  • Stale PN sessions migrate to LID to fix NoSession decryption failures (#475).
  • CompactString for NodeValue / NodeContent / Jid.user (#512); yoke zero-copy node decoding + Jid Server enum (#513); NodeStr replaces Cow<str> (#514).
  • message_enqueue_locks + message_queues merged into ChatLane (#516).

Reliability

  • Graceful shutdown for detached tasks fixes WASM setTimeout leaks (#560).
  • Concurrent disconnect hang resolved via flush_scope counter and socket teardown (#577).
  • In-flight delivery receipts flushed on disconnect (#573).
  • Read-loop blocking + keepalive blind spots fixed (#485).
  • Silent pkmsg drop during offline/online transition fixed (#443).
  • Audit follow-ups across correctness/safety/perf (#460, #511, #584).
  • <unavailable> alongside <enc> no longer swallowed (#589).
  • LID↔PN zombie path closed for group-prekey 406 latency spikes (#579).
  • Centralised timestamps via wacore::time with clippy enforcement (#588, #611).

Contributors

Thanks to everyone who shipped code into this release:

  • @jlucaso1 — the lion's share of receive/send/signal/perf work, group features, pairing, history sync, MEX dispatcher, and the WA Web compliance pass.
  • @Salientekill — timestamp centralisation, typed membership-request variants, group call detection, blocklist LID/pn_jid fix, Arc<wa::Message> sharing.
  • @mcaldas — <unavailable> handling via PDO instead of silent drops.
  • @aldoeliacim — public refresh_prekeys() for device migration.
  • @Copilot — targeted single-device DM retry refactor matching WA Web.
  • @dependabot — kept dependencies current across the release window (aes, rand, rustls, compact_str, yoke, libsqlite3-sys).

New contributors


Full changelog: v0.5.0...v0.6.0 — over 200 PRs across the receive/send/signal pipelines, group features, pairing, history sync, performance, and WA Web parity.

All PRs in this release (auto-generated, full attribution)
  • perf: optimize e2e CI — release build + skip soak tests by @jlucaso1 in #417
  • fix: media stanza type, enc mediatype, thumbnails, and quote remoteJid by @jlucaso1 in #418
  • refactor: forward media via CDN reuse, simplify main.rs by @jlucaso1 in #419
  • perf: remove upload buffer clone, DRY download params and error patterns by @jlucaso1 in #420
  • perf: hot-path allocations, ProductCatalogImage fix, DRY and dead code cleanup by @jlucaso1 in #421
  • feat: add Client::logout() by @jlucaso1 in #422
  • feat: add message_id to SendOptions by @jlucaso1 in #423
  • feat: add pin_message() / unpin_message() by @jlucaso1 in #424
  • fix: auto-detect stanza metadata for pin/poll/event messages by @jlucaso1 in #425
  • fix: content-based decrypt-fail, poll vote/event response meta, SKDM decrypt-fail by @jlucaso1 in #426
  • feat: auto-detect biz node, fix stanza types, serde-snake-case by @jlucaso1 in #427
  • perf!: unify device resolution through device_registry, eliminate ~700ms cache miss penalty by @jlucaso1 in #428
  • fix: use BadMac (error 7) for MAC failures, treat empty sessions as missing by @jlucaso1 in #429
  • refactor: replace DeviceRow positional tuple with named struct by @jlucaso1 in #434
  • chore: update proto to WhatsApp Web 2.3000.1035617621 by @jlucaso1 in #436
  • Implement end-to-end tests for TC token by @jlucaso1 in #437
  • refactor: make privacy settings API type-safe and WA Web compliant by @jlucaso1 in #438
  • fix: correct stanza type and meta node mappings to match WA Web by @jlucaso1 in #439
  • feat: cstoken (NCT) privacy token fallback + proto update by @jlucaso1 in #433
  • refactor: replace stringly-typed APIs with enums across codebase by @jlucaso1 in #440
  • fix: parse digest prekey IDs from nodes, not by @jlucaso1 in #441
  • feat: add AB props cache and privacy tokens conditional by @jlucaso1 in #442
  • fix: prevent silent pkmsg drop during offline-online semaphore transition by @jlucaso1 in #443
  • feat!: unified per-device sender key tracking (WA Web parity) by @jlucaso1 in #444
  • perf: in-memory cache for sender key device tracking by @jlucaso1 in #445
  • fix: audit findings — prekey wrap-around, chain key overflow, log levels by @jlucaso1 in #446
  • refactor!: unify newsletter send through Client::send_message by @jlucaso1 in #447
  • fix: include meta node in newsletter sends (WA Web parity) by @jlucaso1 in #448
  • feat!: album message support by @jlucaso1 in #449
  • feat: ephemeral message support for send and receive by @jlucaso1 in #450
  • fix: recognize own LID devices as self in DM device partitioning by @jlucaso1 in #451
  • refactor: return SendResult from status send methods by @jlucaso1 in #452
  • feat!: support LID and PN JIDs in is_on_whatsapp by @jlucaso1 in #453
  • feat!: sticker pack sending support by @jlucaso1 in #454
  • perf: sync Mutex for pending_retries, remove redundant Arc clone by @jlucaso1 in #455
  • fix: add missing context to warning logs by @jlucaso1 in #456
  • refactor!: API ergonomics — &[Jid], #[non_exhaustive], PartialEq/Eq by @jlucaso1 in #457
  • fix: add #[must_use] to AbortHandle and clear message queues on disconnect by @jlucaso1 in #459
  • fix: address 9 audit findings across correctness, safety, and performance by @jlucaso1 in #460
  • refactor: simplify Dockerfile with cargo-chef and Alpine by @jlucaso1 in #461
  • fix: per-device session locks in DM send path (#462) by @jlucaso1 in #463
  • fix: use bare Signal address for 1:1 DM recipients by @jlucaso1 in #464
  • fix!: decode key-index-list to filter stale devices from device registry by @jlucaso1 in #469
  • perf!: reduce allocations in upload, send, and Jid paths by @jlucaso1 in #471
  • feat: complete tctoken/cstoken privacy token lifecycle matching WA Web by @jlucaso1 in #473
  • feat: add Signal protocol feature API and consolidate internal helpers by @jlucaso1 in #474
  • fix!: migrate stale PN sessions to LID to fix NoSession decryption failures by @jlucaso1 in #475
  • feat: buffered download fallback when streaming is unavailable by @jlucaso1 in #476
  • fix: reduce log noise during reconnect and offload buffered decrypt by @jlucaso1 in #477
  • refactor: architecture audit — cache bounds, circuit breaker, correctness fixes by @jlucaso1 in #478
  • fix: sync own device list at login and parse server key-index in usync by @jlucaso1 in #479
  • feat: unknown device detection and deferred device sync by @jlucaso1 in #480
  • feat: portable-atomic for 32-bit targets and QR rotation safety guard by @jlucaso1 in #481
  • fix: route session migration through signal cache instead of backend by @jlucaso1 in #482
  • fix: normalize sender key address to bare JID for group messages by @jlucaso1 in #483
  • fix: revert DM send to bare recipient JID (server-side fanout) by @jlucaso1 in #484
  • fix: eliminate read-loop blocking and keepalive blind spots by @jlucaso1 in #485
  • refactor: generic WebSocket transport with from_websocket API by @jlucaso1 in #486
  • feat!: unified LID-PN lookup via get_lid_pn_entry(&Jid) by @jlucaso1 in #487
  • fix: group description parsing, ephemeral trigger, participating roundtrip, business hour types by @jlucaso1 in #488
  • fix: invalidate sender key cache on device changes and handle identity notifications by @jlucaso1 in #489
  • fix: complete identity change handler to match WA Web by @jlucaso1 in #490
  • chore: update nightly to 2026-04-05 and fix new clippy warnings by @jlucaso1 in #491
  • fix!: persist server_has_prekeys flag matching WA Web by @jlucaso1 in #492
  • fix: process group messages from unknown devices instead of discarding by @jlucaso1 in #493
  • fix: validate phash from server ack to detect stale device lists by @jlucaso1 in #494
  • chore: reduce duplication and unnecessary allocations by @jlucaso1 in #495
  • chore: add SenderKeyName::from_jid() and avoid clones in parse_message_info by @jlucaso1 in #496
  • perf: Reduce duplication and allocation churn by @jlucaso1 in #497
  • chore(deps): update sha1, sha2, hkdf, hmac to digest 0.11 ecosystem by @jlucaso1 in #498
  • perf: reduce duplication and unnecessary allocations by @jlucaso1 in #499
  • perf: zero-alloc Jid dedup and session lock helper by @jlucaso1 in #500
  • bench: add full send/receive pipeline benchmarks by @jlucaso1 in #501
  • perf: zero-copy ciphertext serialization and DRY send pipeline by @jlucaso1 in #502
  • perf!: immutable load_sender_key and auto-reserve marshal by @jlucaso1 in #503
  • fix: handle messages via PDO instead of silently dropping by @mcaldas in #506
  • fix: PDO cache key mismatch and add response guards by @jlucaso1 in #509
  • perf: lazy ciphertext in SignalMessage, eliminate redundant allocation by @jlucaso1 in #510
  • fix: codebase audit with bug fixes, race condition mitigations, and perf improvements by @jlucaso1 in #511
  • perf!: use CompactString for NodeValue, NodeContent, and Jid.user by @jlucaso1 in #512
  • perf!: yoke zero-copy node decoding and Jid Server enum by @jlucaso1 in #513
  • perf!: replace Cow with NodeStr for inline decoded strings by @jlucaso1 in #514
  • perf!: Arc event bus to eliminate deep clones on dispatch by @jlucaso1 in #515
  • perf!: merge message_enqueue_locks + message_queues into ChatLane by @jlucaso1 in #516
  • perf: single-buffer ProtocolAddress + reusable hot-loop address construction by @jlucaso1 in #518
  • perf!: reduce hot-path allocations in receipt, send, and signal paths by @jlucaso1 in #519
  • perf!: Arc across message, retry, and PDO paths by @jlucaso1 in #520
  • fix: suppress ack errors for all transport-unavailable conditions by @jlucaso1 in #521
  • perf!: zero-copy receive pipeline and take-ownership session cache by @jlucaso1 in #522
  • feat!: comprehensive group feature improvements by @jlucaso1 in #527
  • perf!: reduce startup allocations by ~21% and peak heap by ~31% by @jlucaso1 in #525
  • chore(deps): bump rand from 0.10.0 to 0.10.1 by @dependabot[bot] in #531
  • chore(deps): bump rustls from 0.23.37 to 0.23.38 by @dependabot[bot] in #530
  • chore(deps): bump compact_str from 0.8.1 to 0.9.0 by @dependabot[bot] in #528
  • fix: WA Web-compliant DM multi-device fanout with phash validation by @jlucaso1 in #524
  • fix: centralize timestamp handling via wacore::time and fix signed parsing by @Salientekill in #532
  • feat!: replace history sync events with lazy blob + perf optimizations by @jlucaso1 in #533
  • feat(groups): add typed membership request variants (correct wire format) by @Salientekill in #534
  • chore(deps): bump aes from 0.8.4 to 0.9.0 by @dependabot[bot] in #529
  • feat: add extension points for proxy and custom TLS support by @jlucaso1 in #536
  • chore(deps): bump libsqlite3-sys from 0.35.0 to 0.36.0 by @dependabot[bot] in #431
  • fix: replace tokio timeout with runtime-agnostic timeout in phash validation by @jlucaso1 in #537
  • feat(wacore-binary): zero-copy Serialize for NodeRef type family by @jlucaso1 in #539
  • feat(prekeys): expose public refresh_prekeys() for device migration by @aldoeliacim in #538
  • perf: eliminate fmt::Write dispatch from JID string formatting by @jlucaso1 in #540
  • feat(status): add send_reaction for status likes by @jlucaso1 in #541
  • perf: skip string classification for strings longer than PACKED_MAX by @jlucaso1 in #542
  • perf: unify single/double-byte token maps into single PHF lookup by @jlucaso1 in #543
  • perf!: replace PHF+SipHash token lookup with hashify PTHash (FNV-1a) by @jlucaso1 in #544
  • perf: avoid unnecessary clone and pre-allocate Vecs in hot paths by @jlucaso1 in #545
  • perf: trim unused features, deps, and gate prost-build behind feature by @jlucaso1 in #547
  • refactor: targeted single-device DM retry (matches WA Web) by @Copilot in #549
  • fix: WA Web-compliant DM retry handler (chat/requester separation + alternate key lookup) by @jlucaso1 in #550
  • perf: integration benchmarks + allocation optimizations (-23% total, -43% connect allocs) by @jlucaso1 in #551
  • perf: zero-copy frame send, direct prekey encoding, covariant AttrsRef by @jlucaso1 in #552
  • perf: split notification handler, boxed-slice children, interest-based props by @jlucaso1 in #553
  • perf: signal store hot path - lightweight PN, cache-first identity, reusable flush buffer by @jlucaso1 in #554
  • perf: pre-encode acks, Bytes for sessions, [u8;32] for identities by @jlucaso1 in #555
  • WA Web compliance: retry flow + group/status session rebuild by @jlucaso1 in #559
  • Graceful shutdown for detached tasks (fixes WASM setTimeout leaks) by @jlucaso1 in #560
  • feat: pluggable SignalCryptoProvider for AES-CBC/GCM + HMAC-SHA256 by @jlucaso1 in #561
  • feat: incoming call events + member label update by @jlucaso1 in #562
  • fix: status reactions via send_message with correct E2EE wire format by @jlucaso1 in #563
  • fix: participant change success check + phone/username mixins by @jlucaso1 in #564
  • fix: cache-aside fallback in get_lid_pn_entry by @jlucaso1 in #565
  • Align enum JSON discriminators with wire tags via StringEnum by @jlucaso1 in #566
  • Unify enum wire-tag derives under #[derive(WireEnum)] by @jlucaso1 in #567
  • fix(status): use LID addressing, skip unresolvable recipients by @jlucaso1 in #568
  • fix(status): drop on reactions and revokes by @jlucaso1 in #569
  • perf: 11 allocation trims from hot-path audit by @jlucaso1 in #570
  • fix(receipt): flush in-flight delivery receipts on disconnect by @jlucaso1 in #573
  • feat(mex): notification dispatcher + consolidated doc-id registry by @jlucaso1 in #574
  • refactor(receipt): extract FlushScope primitive; fix PDO leak via shutdown signal by @jlucaso1 in #576
  • fix: concurrent disconnect hang (flush_scope counter leak + cleanup_connection_state socket teardown) by @jlucaso1 in #577
  • perf(message): off-load LID-PN persist + migrations from the decrypt hot path by @jlucaso1 in #578
  • fix(send): close LID↔PN zombie path for group prekey 406 latency spikes by @jlucaso1 in #579
  • chore: audit follow-ups (perf, cleanup, helpers) by @jlucaso1 in #584
  • chore(deps): bump yoke from 0.7.5 to 0.8.2 by @dependabot[bot] in #583
  • fix(pdo): align PDO recovery + UndecryptableMessage dispatch with WA Web by @jlucaso1 in #585
  • feat(device)!: DevicePropsOverride builder, align DEVICE_PROPS with WA Web by @jlucaso1 in #586
  • fix(http): raise UreqHttpClient body cap from ureq's 10 MiB default by @jlucaso1 in #587
  • fix(time): route all now() calls through wacore::time; enforce via clippy by @jlucaso1 in #588
  • fix(decrypt): fall through when comes alongside by @jlucaso1 in #589
  • perf: reduce cache and channel pre-allocations by @jlucaso1 in #590
  • [codex] fix e2e reconnect and receipt flakes by @jlucaso1 in #591
  • fix(pair-code)!: derive companion_platform_{id,display} from DeviceProps by @jlucaso1 in #592
  • fix(pair): WA Web compliant pairing QR + companion_platform_{id,display} by @jlucaso1 in #593
  • fix(pair,props): enforce ADV HMAC verify and round-trip children by @jlucaso1 in #594
  • fix(pair): emit empirically correct companion_platform_id for Android by @jlucaso1 in #595
  • feat(client): add ClientProfile for noise-handshake identity by @jlucaso1 in #596
  • refactor!: preserve typed error sources across the workspace by @jlucaso1 in #597
  • feat(noise): implement Noise_IK + XXfallback for WA-Web parity by @jlucaso1 in #598
  • feat(call): detect group calls via offer_notice and group-jid attrs by @Salientekill in #599
  • fix(pair)!: map Android PlatformType to Chrome, drop Unknown variant by @jlucaso1 in #601
  • fix(blocklist): include LID + pn_jid in block IQ (modern WA) by @Salientekill in #600
  • fix(proto_helpers): detect view_once inline flag and nested wrappers by @jlucaso1 in #602
  • fix(send): always distribute SKDM on first group send + stop wiping tracker on identity change by @jlucaso1 in #603
  • fix(send): close SKDM flow gaps for forward secrecy and cache hygiene by @jlucaso1 in #604
  • fix(lid_pn): WA Web compliant signal address for Hosted JIDs by @jlucaso1 in #605
  • fix(lid_pn): keep status@broadcast resolving to @lid by @jlucaso1 in #609
  • refactor(time): split monotonic clock from wall clock by @jlucaso1 in #611
  • fix(portable_cache): add iter() to mirror moka::Cache by @jlucaso1 in #612
  • perf(send): parallelize group encrypt fan-out + adjacent wins by @jlucaso1 in #610
  • perf(events): share wa::Message via Arc end-to-end (zero deep-clone on dispatch) by @Salientekill in #613
  • feat(history_sync): expose peer_data_request_session_id on LazyHistorySync by @jlucaso1 in #614
  • feat(groups): return full GroupMetadata from create_group / community.create by @jlucaso1 in #615
  • feat(message_edit): decrypt secretEncryptedMessage MESSAGE_EDIT envelope by @jlucaso1 in #618
  • chore(release): bump workspace to 0.6.0 by @jlucaso1 in #619
  • ci(release): install cargo-release via taiki-e action by @jlucaso1 in #620

Don't miss a new whatsapp-rust release

NewReleases is sending notifications on new releases.