Skip to main content
USync (“user sync”) is the WhatsApp protocol used to batch-query per-user data: registration status, device lists, profile picture/status, business verification, bot profiles, and more. Higher-level helpers like 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 lookups
  • Full — user info with more detail
  • Delta — incremental contact synchronization
UsyncContext:
  • Interactive (default) — user-initiated operations
  • Background — background sync
  • Message — message-related operations
  • Voip — call setup refreshing device lists
Neither UsyncMode nor UsyncContext is #[non_exhaustive]. Delta and Voip are new variants added in this release — an exhaustive match over either enum in existing code will fail to compile until the new arms are handled.

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

Each per-user protocol result is wrapped in 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:
Payload structs, all #[non_exhaustive]:
  • UsyncContactResultcontact_type: CompactString, username: Option<CompactString>, content: Option<CompactString>
  • UsyncDevicesResultdevice_list: Option<UsyncDeviceListResult>, key_index: Option<UsyncKeyIndexResult>
    • UsyncDeviceListResulthash: Option<CompactString>, devices: Vec<UsyncDeviceResult>
    • UsyncDeviceResultid: u16, key_index: Option<u32>, is_hosted: bool
    • UsyncKeyIndexResulttimestamp: i64, signed_key_index_bytes: Option<Vec<u8>>, expected_timestamp: Option<i64>
  • UsyncStatusResultstatus: Option<CompactString>, timestamp: Option<i64> (WhatsApp Web itself only consumes status; the wire timestamp is kept for callers that need it)
  • UsyncTextStatusResulttext, emoji, ephemeral_duration_seconds, last_update_time (all Option)
  • UsyncDisappearingModeResultduration_seconds: u32, setting_timestamp: i64, ephemerality_disabled: bool
  • UsyncBusinessResultverified_name: Option<VerifiedName> (see is_on_whatsapp for VerifiedName fields)
  • UsyncFeatureResultfeature: UsyncFeature, value: CompactString
  • UsyncBotProfileResultname, 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>
    • UsyncBotPromptemoji: CompactString, text: CompactString
    • UsyncBotCommandname: CompactString, description: CompactString
    • UsyncBotProfessionalTypeUnknown, Yes, No, or an Other(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) and UsyncDevice (wacore::usync::UsyncDevice) both gained an is_hosted: bool field. 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 on is_hosted as well. Persisted DeviceInfo JSON without is_hosted still deserializes correctly (is_hosted defaults to false) — this only affects Rust construction and pattern-matching call sites, not on-disk data.
  • UsyncMode::Delta and UsyncContext::Voip are new enum variants (see the warning above).