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. ShipsMessageEdits::decryptand a generalisedsecret_enc_addonhelper 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-messagemessage_idinSendOptions(#423). - Incoming call events + group call detection (#562, #599).
ClientProfilefor 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_websockettransport 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
LazyHistorySyncyou decode on demand. - Full
GroupMetadatareturned bycreate_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),
NodeStrreplacingCow<str>for inline decoded strings (#514),Arc<wa::Message>end-to-end on dispatch (#613), andArc<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:CompactStringforJid.user,NodeValue,NodeContent(#512); zero-copySerializefor theNodeReffamily (#539); zero-alloc dedup (#500); eliminatedfmt::Writedispatch 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);
Bytesfor 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_idderivation, 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@broadcastresolves to@lid(#609). - Identity change handler completed to match WA Web (#489, #490).
- PDO recovery +
UndecryptableMessagedispatch (#585),<unavailable>via PDO (#506). - Stale device filtering via decoded
key-index-list(#469); sender-key cache invalidation on device changes (#489). view_oncenested-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 replaceVec<Jid>across feature APIs (#457).#[non_exhaustive]added to event and option enums (#457) — new variants no longer break downstreammatches.- Typed errors preserve sources across the workspace (#597) —
anyhow::Errorreturns replaced bythiserrorenums 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-listdecoded to filter stale devices (#469).server_has_prekeysflag persisted to match WA Web (#492).- Immutable
load_sender_keyand auto-reserve marshal (#503).
Device / pair
DevicePropsOverridebuilder;DEVICE_PROPSaligned with WA Web (#586).companion_platform_{id,display}now derived fromDeviceProps(#592, #593).PlatformType::Androidmaps to Chrome;PlatformType::Unknownremoved (#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
NoSessiondecryption failures (#475). CompactStringforNodeValue/NodeContent/Jid.user(#512); yoke zero-copy node decoding +Jid Serverenum (#513);NodeStrreplacesCow<str>(#514).message_enqueue_locks+message_queuesmerged intoChatLane(#516).
Reliability
- Graceful shutdown for detached tasks fixes WASM
setTimeoutleaks (#560). - Concurrent disconnect hang resolved via
flush_scopecounter and socket teardown (#577). - In-flight delivery receipts flushed on disconnect (#573).
- Read-loop blocking + keepalive blind spots fixed (#485).
- Silent
pkmsgdrop 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::timewith 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
- @mcaldas — first contribution in #506
- @Salientekill — first contribution in #532
- @aldoeliacim — first contribution in #538
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