is_on_whatsapp, get_user_info, and get_user_devices already build on USync internally.
Client::query_usync exposes the same typed query engine directly, for protocol combinations the specialized helpers don’t cover — for example fetching a bot’s profile, resolving a username, or reading disappearing_mode/text_status in the same request as a device-list lookup.
Prefer the specialized helpers (
Contacts, Signal::get_user_devices via client.signal()) for common lookups — they also handle cache population and persistence. query_usync is a neutral operation: it only returns decoded wire data.Access
query_usync is a direct method on Client (not behind a sub-accessor):
Building a query
UsyncQuery::new validates the whole query before it reaches the network. It requires at least one protocol and one user, and rejects duplicate protocol kinds. It also enforces per-user field consistency — for example, a tc_token requires the Status protocol to be selected, and device_sync requires DevicesV2. Deserializing a UsyncQuery from an external source runs this same validation, so a serialized input can’t bypass it.
UsyncMode:
Query(default) — contact lookupsFull— user info with more detailDelta— incremental contact synchronization
UsyncContext:
Interactive(default) — user-initiated operationsBackground— background syncMessage— message-related operationsVoip— call setup refreshing device lists
UsyncUser
Construct a query target from a JID, phone number, or username, then attach protocol-specific inputs with builder methods:
from_phone/with_phone accept a digit-only phone string and canonicalize it to E.164 (+-prefixed) form automatically. A phone number is rejected by UsyncQuery::new (UsyncValidationError::InvalidPhone) if it has a leading zero, contains non-digit characters after the +, or exceeds 15 digits.UsyncDeviceSyncHint carries cache hints for the DevicesV2 subprotocol so the server can skip returning an unchanged device list:
UsyncProtocol
UsyncAddressingMode is Pn (default) or Lid. UsyncFeature lists the feature flags the Features subprotocol can query (Document, Encrypt, EncryptBlocklist, EncryptContact, EncryptGroupGen2, EncryptImage, EncryptLocation, EncryptUrl, EncryptV2, Voip, MultiAgent).
Reading the response
UsyncOutcome<T>, which is either the decoded value or the server’s per-subprotocol error — matching the same “errors don’t fail the whole batch” behavior as is_on_whatsapp/get_user_info:
UsyncProtocolResult carries the typed payload per protocol:
#[non_exhaustive]:
UsyncContactResult—contact_type: CompactString,username: Option<CompactString>,content: Option<CompactString>UsyncDevicesResult—device_list: Option<UsyncDeviceListResult>,key_index: Option<UsyncKeyIndexResult>UsyncDeviceListResult—hash: Option<CompactString>,devices: Vec<UsyncDeviceResult>UsyncDeviceResult—id: u16,key_index: Option<u32>,is_hosted: boolUsyncKeyIndexResult—timestamp: i64,signed_key_index_bytes: Option<Vec<u8>>,expected_timestamp: Option<i64>
UsyncStatusResult—status: Option<CompactString>,timestamp: Option<i64>(WhatsApp Web itself only consumesstatus; the wire timestamp is kept for callers that need it)UsyncTextStatusResult—text,emoji,ephemeral_duration_seconds,last_update_time(allOption)UsyncDisappearingModeResult—duration_seconds: u32,setting_timestamp: i64,ephemerality_disabled: boolUsyncBusinessResult—verified_name: Option<VerifiedName>(seeis_on_whatsappforVerifiedNamefields)UsyncFeatureResult—feature: UsyncFeature,value: CompactStringUsyncBotProfileResult—name,attributes,description,category,is_default,prompts: Vec<UsyncBotPrompt>,persona_id,commands: Vec<UsyncBotCommand>,commands_description,is_meta_created: Option<bool>,creator_name: Option<CompactString>,creator_profile_url: Option<CompactString>,posing_as_professional: Option<UsyncBotProfessionalType>UsyncBotPrompt—emoji: CompactString,text: CompactStringUsyncBotCommand—name: CompactString,description: CompactStringUsyncBotProfessionalType—Unknown,Yes,No, or anOther(String)catch-all for unrecognized wire values
Example
Validation errors
Client::query_usync surfaces a validation failure as IqError::EncodeError (the query never reaches the network).
Hosted addressing
UsyncDeviceResult.is_hosted (and the corresponding is_hosted field on the persisted DeviceInfo/UsyncDevice types — see Store: DeviceListRecord) marks a device as belonging to WhatsApp’s hosted PN/LID address space rather than the regular one. Use Jid::with_device_hosting(device_id, is_hosted) to build a correctly-addressed device JID from a device-list entry:
Breaking changes
DeviceInfo(wacore::store::traits::DeviceInfo) andUsyncDevice(wacore::usync::UsyncDevice) both gained anis_hosted: boolfield. This breaks both construction and exhaustive pattern matching. Struct-literal construction (DeviceInfo { device_id, key_index }) no longer compiles — use the new constructors instead:An exhaustive destructuring pattern (let DeviceInfo { device_id, key_index } = info;) also no longer compiles — add a..to the pattern (let DeviceInfo { device_id, key_index, .. } = info;) or match onis_hostedas well. PersistedDeviceInfoJSON withoutis_hostedstill deserializes correctly (is_hosteddefaults tofalse) — this only affects Rust construction and pattern-matching call sites, not on-disk data.UsyncMode::DeltaandUsyncContext::Voipare new enum variants (see the warning above).