Contacts struct provides methods for checking WhatsApp registration status and retrieving profile pictures and user information.
Access
Access contact operations through the client:Methods
is_on_whatsapp
Check if JIDs are registered on WhatsApp. Accepts both PN JIDs and LID JIDs.jids- Array of JIDs to check. SupportsJid::pn("phone_number")for phone number lookups andJid::lid("lid_value")for LID lookups. If you pass a non-PN/non-LID JID (groups, newsletters, etc.), the method returns an error immediately.
Vec<IsOnWhatsAppResult>- Registration status for each JID
jid: Jid- WhatsApp JID for the useris_registered: bool- Whether the JID is on WhatsApplid: Option<Jid>- LID (Linked Identity) if availablepn_jid: Option<Jid>- Phone number JID, present when the server returns LID as the primary JIDis_business: bool- Whether this is a WhatsApp Business accountverified_name: Option<VerifiedName>- Decoded verified business name certificate, when the account is a verified businessusername: Option<CompactString>- Meta username, without the display-only@prefix.Nonemeans the server reported no username, which is also how it reports one that was deleted; it is never an empty stringcontact_error: Option<UsyncSubprotocolError>- Server error for thecontactsubprotocol (e.g. privacy-blocked lookup);is_registeredwill befalselid_error: Option<UsyncSubprotocolError>- Server error for thelidsubprotocol;lidwill beNonebusiness_error: Option<UsyncSubprotocolError>- Server error for thebusinesssubprotocol;is_businesswill befalseusername_error: Option<UsyncSubprotocolError>- Server error for theusernamesubprotocol. Unlike the other*_errorfields, this does not implyusernameisNone. The server also publishes a username as a plain attribute of thecontactresult, and a failed subprotocol does not retract that value. Sousernamecan still beSomenext tousername_error.
name: Option<String>- Display name shown to other users (decoded from the certificate when the server omits the attribute)serial: Option<String>- Certificate serial numberissuer: Option<String>- Certificate issuercertificate: Option<Vec<u8>>- RawVerifiedNameCertificateprotobuf bytes, for callers that need to verify the signature themselves
IsOnWhatsAppResult is marked #[non_exhaustive], so new fields may be added in future versions without a breaking change.Both
is_on_whatsapp and get_user_info now ask for the username subprotocol by default, matching WhatsApp Web’s own existence check and background contact sync. This is not opt-in and adds one more child to the <query> node of both requests.get_profile_picture
Get the profile picture URL for a JID.jid- Target JID (user, group, or newsletter)preview-truefor preview thumbnail,falsefor full-size image
Option<ProfilePicture>- Picture info orNoneif not available
id: String- Picture IDurl: String- Download URLdirect_path: Option<String>- Direct path for media downloadhash: Option<String>- SHA-256 hash for integrity and cache validation
To override the default request timeout for a single fetch, use
get_profile_picture_with_timeout(jid, preview, timeout), which takes an extra timeout: Option<Duration> argument. Internally the request is built via ProfilePictureSpec’s with_timeout(...) builder method; pass None to fall back to the default timeout.The system/announcements JID (
0@s.whatsapp.net, and its legacy form 0@c.us) never answers this IQ, so it’s short-circuited client-side: both get_profile_picture and get_profile_picture_with_timeout return Ok(None) immediately for it instead of waiting out the full request timeout. This mirrors WhatsApp Web, which never sends the request for this JID in the first place.get_user_info
Get user information by JID.jids- Array of JIDs to query
The system/announcements JID (
0@s.whatsapp.net, and its legacy form 0@c.us) is filtered out of the batch before it’s sent — it’s not usync-eligible and the server never answers for it. If it’s the only JID passed in, get_user_info returns an empty map without making a request; if it’s mixed with other JIDs, the remaining JIDs are still queried normally.HashMap<Jid, UserInfo>- Map of JID to user info
jid: Jid- WhatsApp JIDlid: Option<Jid>- LID if availablelid_error: Option<UsyncSubprotocolError>- Server error for thelidsubprotocol;lidwill beNonestatus: Option<String>- Status messagestatus_error: Option<UsyncSubprotocolError>- Server error for thestatussubprotocol (e.g. privacy-hidden status);statuswill beNonepicture_id: Option<String>- Profile picture IDpicture_error: Option<UsyncSubprotocolError>- Server error for thepicturesubprotocol;picture_idwill beNoneis_business: bool- Whether business accountbusiness_error: Option<UsyncSubprotocolError>- Server error for thebusinesssubprotocol;is_businesswill befalseverified_name: Option<VerifiedName>- Decoded verified business name certificate, for verified business accounts (seeis_on_whatsappfor field details)devices: Vec<u16>- Device IDs from the<devices version="2">sublist the same usync query returns (device0is the primary). Empty when the server omits the sublist — no extra request is needed.devices_error: Option<UsyncSubprotocolError>- Server error for thedevicessubprotocol;deviceswill be emptyusername: Option<CompactString>- Meta username, without the display-only@prefix. SeeIsOnWhatsAppResult::usernamefor how it’s sourced and whatNonemeansusername_error: Option<UsyncSubprotocolError>- Server error for theusernamesubprotocol. SeeIsOnWhatsAppResult::username_error— a value already read from thecontactresult’s attribute survives next to this error
UserInfo is #[non_exhaustive], so new fields may be added in future versions without a breaking change.code: Option<u16>- Numeric error code from the server (e.g.403,404)text: Option<String>- Human-readable error descriptionbackoff: Option<u32>- Server-suggested retry delay in seconds
*_error fields when a corresponding Option field is None and you need to distinguish “not set” from “server error”.
find_by_username
Resolve a Meta username to the account behind it.username- The handle to resolve. A leading@is display-only and is stripped automatically. The bare handle must be betweenUSERNAME_MIN_LENGTH(3) andUSERNAME_MAX_LENGTH(35) characters, or the call fails withContactError::Usernamebefore any request is sent.username_key- The account’s numeric “username key” (WhatsApp Web calls it a pin). Some accounts require it before the server will disclose the identity behind their handle at all.
UsernameLookup:
NotFound- No account answers to this username, or it’s not reachable from hereKeyRequired { username: Option<CompactString> }- The username exists but the server withheld the identity; repeat the call with the account’s username keyFound(UsernameLookupUser)- The username resolved to an account
jid: Jid- Identity the server returned. The query addresses contacts by LID, so this is normally a LIDpn_jid: Option<Jid>- Phone-number JID, when the server disclosed one on<business>username: Option<CompactString>- Username as the server spelled it backis_business: boolverified_name: Option<VerifiedName>
Found, the discovered LID/PN pair is persisted to the local cache, the same way is_on_whatsapp and get_user_info do.
Example:
Privacy & TC tokens
For user JIDs (not groups/newsletters), the library automatically includes TC tokens when fetching profile pictures. TC tokens are used for privacy-gated operations. The implementation automatically:- Looks up TC tokens for user JIDs
- Includes tokens in profile picture requests
- Skips tokens for groups and newsletters
get_user_info does the same for status/about: when the profile_scraping_privacy_token_in_about_usync AB prop is on, each queried JID’s TC token is attached to its <user> node in the usync IQ, matching WhatsApp Web’s USyncStatusProtocol. This is what lets status/status_error and about resolve correctly for a privacy-restricted contact instead of coming back hidden. See TC Token for details.
Async compatibility
is_on_whatsapp and get_user_info work correctly when called from #[async_trait] implementations or any context that boxes the returned future (Box<dyn Future + Send>). Earlier versions produced a compile error ("implementation of FnOnce is not general enough") that could not be worked around in user code. Fixed in #826 with no API changes.
Error handling
All methods returnResult<T, ContactError>:
Username is returned by find_by_username when the handle can’t be turned into a valid lookup — too short, too long, or a username_key that fails usync’s own field-consistency validation.
Batch operations
All lookup methods support batch operations for efficiency:Contact notification events
The server sendscontacts notifications when contact data changes. These are emitted as events you can subscribe to:
ContactUpdated— a contact’s profile changed (invalidate cached presence/profile picture)ContactNumberChanged— a contact changed their phone number (includes old/new JID and optional LID mappings)ContactSyncRequested— the server requests a full contact re-sync
These are server-push notifications, distinct from
ContactUpdate and ContactRemoved, which come from app-state sync mutations made on a linked device. See Chat actions — Save and remove contacts for save_contact/remove_contact and their events.Empty input handling
Passing empty arrays returns empty results without making network requests:Migration from previous versions
Theis_on_whatsapp method previously accepted &[&str] (phone number strings). It now takes &[Jid]:
get_info method and ContactInfo type have been removed. Use is_on_whatsapp for registration checks (now includes lid, pn_jid, and is_business fields) or get_user_info for detailed profile data (status, picture ID).