> ## Documentation Index
> Fetch the complete documentation index at: https://whatsapp-rust.jlucaso.com/llms.txt
> Use this file to discover all available pages before exploring further.

# June 9, 2026 — Protocol error-handling strictness

> Several protocol paths that previously swallowed errors or returned ambiguous results now signal failures explicitly. is_on_whatsapp rejects unsupported JID types, per-subprotocol errors are preserved on IsOnWhatsAppResult and UserInfo, device-list/LID batch queries degrade per user, and IQ responses with unexpected type are rejected.

## Breaking changes

**`is_on_whatsapp` rejects unsupported JID types ([#817](https://github.com/oxidezap/whatsapp-rust/pull/817))**

Passing a non-PN/non-LID JID (group, newsletter, etc.) to `contacts().is_on_whatsapp()` now returns an error immediately. Previously the method silently dropped those JIDs with a `warn!` log, making it easy to miss the ignored input.

```rust theme={null}
// This now returns Err("is_on_whatsapp only supports PN and LID JIDs, got …")
client.contacts().is_on_whatsapp(&[Jid::group("120363021033254949")]).await?;
```

Validate that your inputs to `is_on_whatsapp` contain only `Jid::pn(...)` or `Jid::lid(...)` values.

**`IqError::UnexpectedResponseType` added ([#817](https://github.com/oxidezap/whatsapp-rust/pull/817))**

IQ responses whose `type` attribute is neither `result` nor `error` (e.g. `get`, `set`, or absent) now produce `IqError::UnexpectedResponseType { got: Option<String> }` instead of being silently accepted. If you exhaustively match on `IqError`, add an arm for this variant. It is classified as a transient keepalive failure.

## New fields

**Per-subprotocol error fields on `IsOnWhatsAppResult` and `UserInfo` ([#817](https://github.com/oxidezap/whatsapp-rust/pull/817))**

The usync response parser now preserves server-returned per-subprotocol errors instead of silently omitting the associated field. Both result types gain new `Option<UsyncSubprotocolError>` fields:

`IsOnWhatsAppResult` gains:

* `contact_error` — server error for the contact lookup subprotocol
* `lid_error` — server error for the LID resolution subprotocol
* `business_error` — server error for the business-info subprotocol

`UserInfo` gains:

* `lid_error`, `status_error`, `picture_error`, `business_error`, `devices_error`

`UsyncSubprotocolError` is a new public type:

```rust theme={null}
pub struct UsyncSubprotocolError {
    pub code: Option<u16>,
    pub text: Option<String>,
    pub backoff: Option<u32>,
}
```

When a subprotocol error is present its corresponding data field (`lid`, `status`, `picture_id`, etc.) will be `None` / `false` / empty. Check `*_error` when you need to distinguish "not returned" from "server refused".

Both structs are `#[non_exhaustive]`; add `..` to any exhaustive struct destructuring that matches them.

## Fixes & hardening

**Device-list and LID batch queries degrade per user ([#817](https://github.com/oxidezap/whatsapp-rust/pull/817))**

`DeviceListSpec` and `LidQuerySpec` now skip individual users whose `<devices>` or `<lid>` subprotocol node contains an error, rather than letting that error propagate and fail the entire batch. A server-side per-user error (e.g. a temporarily unavailable device list for one noisy participant) no longer blocks sends to the rest of the group. A result-level `devices` subprotocol error is demoted to a `warn!` log rather than an abort, matching WA Web behavior.

**App-state MAC validation ignores `hasMissingRemove` bypass ([#817](https://github.com/oxidezap/whatsapp-rust/pull/817))**

`hasMissingRemove` was previously used to skip snapshot and patch MAC validation when a REMOVE mutation was missing its prior value, matching a misread of WA Web's behavior. WA Web actually tracks this flag only as telemetry for MAC-failure diagnostics and always rejects MAC mismatches. The bypass is removed; MAC mismatches are now fatal regardless of `hasMissingRemove`.

**Retry receipts: peer-device retry without `recipient` is aborted ([#817](https://github.com/oxidezap/whatsapp-rust/pull/817))**

A retry receipt from a peer device (your own linked device) that omits the `recipient` attribute has no resolvable target chat — WA Web returns `null` in this case. Previously the library logged a warning and fell back to `from.to_non_ad()`, which would produce a failing message lookup anyway. The receipt is now silently dropped rather than proceeding with a wrong chat target.

**Retry key bundles require a one-time prekey for regular retries ([#817](https://github.com/oxidezap/whatsapp-rust/pull/817))**

Regular retry receipts (non-FBID-bot) must include a `<key>` (one-time prekey) child in the `<keys>` bundle. Bundles missing the one-time prekey are now rejected with an explicit error. The exception is primary `@bot` FBID bot retries, which follow WA Web's `bot_retry` parser path and may proceed with identity + signed prekey alone.

**Rejected key bundles abort retry resend ([#817](https://github.com/oxidezap/whatsapp-rust/pull/817))**

When a `<keys>` node is present but the key bundle is rejected (e.g. ADV validation failure, missing prekey), the retry resend is now aborted immediately. Previously the code logged a warning and continued, potentially sending with a stale or wrong session.

**AD-JID decoder rejects unknown domain type bytes ([#817](https://github.com/oxidezap/whatsapp-rust/pull/817))**

The binary-protocol AD-JID decoder now returns a parse error for domain type bytes that do not map to a known server (the WA Web `decodeJidU` behavior). Previously unknown bytes silently mapped to PN, masking malformed protocol frames.

**ADV `NoAccountKey` log downgraded to `debug` ([#817](https://github.com/oxidezap/whatsapp-rust/pull/817))**

The log emitted when a companion device's pre-key bundle or retry bundle omits `account_signature_key` and no stored account identity is available has been downgraded from `warn` to `debug`. This server shape is legitimately common when no account identity has been cached yet; the `warn` level created noise without being actionable.
