Client struct is the core of whatsapp-rust, managing connections, encryption, state, and all protocol-level operations.
Overview
The Client handles:- WebSocket connection lifecycle and automatic reconnection
- Noise Protocol handshake and encryption
- Signal Protocol E2E encryption for messages
- App state synchronization
- Device state persistence
- Event dispatching
Most users should use the
Bot builder instead of creating a Client directly. The Bot provides a simplified API with sensible defaults.Creating a Client
Async runtime for spawning tasks, sleeping, and blocking operations
State manager for device credentials, sessions, and app state
Factory for creating WebSocket connections
HTTP client for media operations and version fetching
Optional WhatsApp version override (primary, secondary, tertiary)
Returns the client Arc and a receiver for history/app state sync tasks
Creating with custom cache configuration
Connection Management
run
disconnect()orlogout()is called- Auto-reconnect is disabled and connection fails
- Client receives a fatal stream error (401 unauthorized, 409 conflict, or 516 device removed)
connect
TRANSPORT_CONNECT_TIMEOUT), matching WhatsApp Web’s MQTT and DGW connect timeout defaults. Without this, a dead network would block on the OS TCP SYN timeout (~60-75s).
The Noise handshake response also has a separate 20-second timeout (NOISE_HANDSHAKE_RESPONSE_TIMEOUT).
Errors:
ClientError::AlreadyConnected- Already connected- Transport connect timeout (20s exceeded)
- Version fetch timeout (20s exceeded)
- Noise handshake timeout or failure
- Connection/handshake failures
logout
LoggedOut event.
Example:
- Disables auto-reconnect
- Sends a
RemoveCompanionDeviceSpecIQ to deregister the companion device (if connected) - Disconnects the transport
- Emits
Event::LoggedOutwithreason: ConnectFailureReason::LoggedOut
disconnect
cleanup_connection_state(), which resets all connection-scoped state — including invalidating per-chat message queues so stale workers exit, clearing the signal cache, draining pending IQ waiters, and resetting offline sync state. The cleanup runs exactly once in run() after the message loop exits, avoiding duplicate cleanup calls. See disconnect cleanup for the full list of resources cleaned up.
reconnect
- Handling network changes (e.g., Wi-Fi to cellular)
- Forcing a fresh server session
- Testing offline message delivery
reconnect_immediately
reconnect(), this sets the expected disconnect flag so the run loop skips the backoff delay.
Example:
wait_for_socket
Maximum time to wait
Ok if socket ready, Err on timeout
wait_for_connected
pair_with_code
Configuration for pair code authentication:
phone_number— Phone number in international format (e.g.,"15551234567")show_push_notification— Whether to show a push notification on the phone (default:true)custom_code— Optional custom 8-character code using Crockford Base32 alphabetplatform_id— Platform identifier for the companion device (default: Chrome)platform_display— Platform display name shown on the phone (default:"Chrome (Linux)")
The 8-character pairing code to display to the user
PairCodeError):
| Variant | Cause |
|---|---|
PhoneNumberRequired | Empty phone number |
PhoneNumberTooShort | Fewer than 7 digits |
PhoneNumberNotInternational | Starts with 0 (not international format) |
InvalidCustomCode | Custom code is not 8 valid Crockford Base32 characters |
MissingPairingRef | Server response missing pairing ref |
RequestFailed | Server request failed |
Connection State
is_connected
true if the Noise socket is established. This method uses an internal AtomicBool flag (with Acquire ordering) instead of probing the noise socket mutex, making it lock-free and immune to false negatives under mutex contention.
Prior to this design, connection checks used
try_lock() on the noise socket mutex. Under contention (e.g., during frame encryption), try_lock() would fail and incorrectly report the client as disconnected — silently dropping receipt acks. The AtomicBool approach eliminates this race condition entirely.is_logged_in
true if authenticated with WhatsApp servers.
Auto-Reconnection
The client includes automatic reconnection handling with Fibonacci backoff.How it works
- On disconnect: The client detects unexpected disconnections and automatically attempts to reconnect
- Fibonacci backoff: Each failed attempt increases the delay following the Fibonacci sequence (1s, 1s, 2s, 3s, 5s, 8s, 13s, 21s…) with a maximum of 900 seconds (15 minutes) and +/-10% jitter
- Expected disconnects: Protocol-expected disconnects (e.g., 515 stream error after pairing) trigger immediate reconnection without backoff
- Keepalive monitoring: A keepalive loop sends periodic pings (every 15-30s) and forces reconnection if the socket appears dead (no data received for 20s after a send)
Controlling auto-reconnect
Stream error handling
The client handles specific<stream:error> codes from the WhatsApp server:
| Stream error code | Meaning | Event emitted | Auto-reconnect |
|---|---|---|---|
| 401 | Session invalidated (unauthorized) | LoggedOut | Disabled — must re-pair |
| 409 | Another client connected (conflict) | StreamReplaced | Disabled — prevents displacement loop |
| 429 | Rate limited (too many connections) | None (emits Disconnected) | Yes, with extended backoff (+5 steps) |
| 503 | Service unavailable | None | Yes, normal backoff |
| 515 | Expected disconnect (e.g., post-pairing) | None | Yes, immediate (no backoff) |
| 516 | Device removed | LoggedOut | Disabled — must re-pair |
| Unknown | Unrecognized code | StreamError | Disabled |
Rate limiting (429)
When the server returns a 429 stream error, the client bumps the internal backoff counter by 5 Fibonacci steps before reconnecting. This means the reconnection delay jumps significantly (e.g., from ~1s to ~13s on the first rate limit) to respect the server’s throttling.General reconnection behavior
| Scenario | Behavior |
|---|---|
| Unexpected disconnect | Reconnect with Fibonacci backoff |
| 515 stream error (after pairing) | Immediate reconnect |
| Keepalive dead socket (20s) | Force disconnect and reconnect |
disconnect() called | No reconnect attempt |
logout() called | No reconnect attempt, device deregistered |
| Auto-reconnect disabled | No reconnect attempt |
Messaging
send_message
Recipient JID (user@s.whatsapp.net or group@g.us)
Protobuf message content
A
SendResult containing the message_id and destination to JIDsend_message_with_options
Configuration for message sending behavior. Supports
message_id (override the auto-generated ID), extra_stanza_nodes (custom XML nodes on the stanza), and ephemeral_expiration (disappearing message duration in seconds).edit_message
ID of the message to edit
New message content
revoke_message
Sender to revoke your own message, or Admin to revoke another user’s message as group admin.
RevokeType::Sender (delete your own message) or RevokeType::Admin { original_sender: Jid } (admin revoke in groups)Feature APIs
The Client provides namespaced access to feature-specific operations:blocking
block(jid: &Jid)- Block a contactunblock(jid: &Jid)- Unblock a contactget_blocklist()- Get all blocked contactsis_blocked(jid: &Jid)- Check if contact is blocked
groups
query_info(jid: &Jid)- Get cached group infoget_metadata(jid: &Jid)- Fetch group metadata from serverget_participating()- List all groups you’re increate_group(options: GroupCreateOptions)- Create a new groupset_subject(jid: &Jid, subject: GroupSubject)- Change group nameset_description(jid: &Jid, desc: Option<GroupDescription>, prev: Option<String>)- Change descriptionleave(jid: &Jid)- Leave a groupadd_participants(jid: &Jid, participants: &[Jid])- Add membersremove_participants(jid: &Jid, participants: &[Jid])- Remove memberspromote_participants(jid: &Jid, participants: &[Jid])- Make members adminsdemote_participants(jid: &Jid, participants: &[Jid])- Remove admin statusget_invite_link(jid: &Jid, reset: bool)- Get/reset invite linkjoin_with_invite_code(code: &str)- Join a group via invite code or URLjoin_with_invite_v4(group_jid, code, expiration, admin_jid)- Accept a V4 invite messageget_invite_info(code: &str)- Preview group metadata from invite codeset_locked(jid: &Jid, locked: bool)- Lock/unlock group info editingset_announce(jid: &Jid, announce: bool)- Enable/disable announcement modeset_ephemeral(jid: &Jid, expiration: u32)- Set disappearing messages timerset_membership_approval(jid: &Jid, mode: MembershipApprovalMode)- Require admin approvalget_membership_requests(jid: &Jid)- Get pending membership requestsapprove_membership_requests(jid: &Jid, participants: &[Jid])- Approve pending requestsreject_membership_requests(jid: &Jid, participants: &[Jid])- Reject pending requestsset_member_add_mode(jid: &Jid, mode: MemberAddMode)- Set who can add membersset_no_frequently_forwarded(jid: &Jid, restrict: bool)- Restrict forwarding of frequently forwarded messagesset_allow_admin_reports(jid: &Jid, allow: bool)- Allow or disallow admin reportsset_group_history(jid: &Jid, enabled: bool)- Enable or disable group history for new membersset_member_link_mode(jid: &Jid, mode: MemberLinkMode)- Set member link modeset_member_share_history_mode(jid: &Jid, mode: MemberShareHistoryMode)- Set history sharing mode for new membersset_limit_sharing(jid: &Jid, enabled: bool)- Limit sharing within the groupcancel_membership_requests(jid: &Jid, participants: &[Jid])- Cancel pending membership requestsrevoke_request_code(jid: &Jid, participants: &[Jid])- Revoke request codes for participantsacknowledge(jid: &Jid)- Acknowledge a groupbatch_get_info(jids: Vec<Jid>)- Batch fetch group metadata for multiple groupsget_profile_pictures(group_jids: Vec<Jid>, picture_type: PictureType)- Batch fetch group profile pictures
presence
set(status: PresenceStatus)- Set presence statusset_available()- Set status to available/onlineset_unavailable()- Set status to unavailable/offlinesubscribe(jid: &Jid)- Subscribe to contact’s presence updatesunsubscribe(jid: &Jid)- Unsubscribe from contact’s presence updates
chatstate
send(to: &Jid, state: ChatStateType)- Send a chat state updatesend_composing(to: &Jid)- Send typing indicatorsend_recording(to: &Jid)- Send recording indicatorsend_paused(to: &Jid)- Send paused/stopped typing indicator
contacts
is_on_whatsapp(jids: &[Jid])- Check if JIDs are registered on WhatsApp (supports PN and LID JIDs)get_user_info(jids: &[Jid])- Get profile info for users by JIDget_profile_picture(jid: &Jid, preview: bool)- Get profile picture URL (preview or full size)
tc_token
issue_tokens(jids: &[Jid])- Request tokens for contactsprune_expired()- Remove expired tokensget(jid: &str)- Get a stored token by JIDget_all_jids()- List all JIDs with stored tokens
chat_actions
archive_chat(jid: &Jid, message_range: Option<SyncActionMessageRange>)- Archive a chatunarchive_chat(jid: &Jid, message_range: Option<SyncActionMessageRange>)- Unarchive a chatpin_chat(jid: &Jid)- Pin a chatunpin_chat(jid: &Jid)- Unpin a chatmute_chat(jid: &Jid)- Mute a chat indefinitelymute_chat_until(jid: &Jid, mute_end_timestamp_ms: i64)- Mute until a specific timeunmute_chat(jid: &Jid)- Unmute a chatstar_message(chat_jid: &Jid, participant_jid: Option<&Jid>, message_id: &str, from_me: bool)- Star a messageunstar_message(chat_jid: &Jid, participant_jid: Option<&Jid>, message_id: &str, from_me: bool)- Unstar a messagemark_chat_as_read(jid: &Jid, read: bool, message_range: Option<SyncActionMessageRange>)- Mark a chat as read or unread across devicesdelete_chat(jid: &Jid, delete_media: bool, message_range: Option<SyncActionMessageRange>)- Delete a chat from all linked devicesdelete_message_for_me(chat_jid: &Jid, participant_jid: Option<&Jid>, message_id: &str, from_me: bool, delete_media: bool, message_timestamp: Option<i64>)- Delete a message locally (not for the other party)
status
send_text(text, background_argb, font, recipients, options)- Post a text statussend_image(upload, thumbnail, caption, recipients, options)- Post an image statussend_video(upload, thumbnail, duration_seconds, caption, recipients, options)- Post a video statussend_raw(message, recipients, options)- Post any message type as a statusrevoke(message_id, recipients, options)- Delete a posted statussend_reaction(status_owner, server_id, reaction)- React to a status update
mex
query(request: MexRequest)- Execute a GraphQL querymutate(request: MexRequest)- Execute a GraphQL mutation
profile
set_push_name(name: &str)- Set display name (syncs across devices)set_status_text(text: &str)- Set profile “About” textset_profile_picture(image_data: Vec<u8>)- Set profile picture (JPEG, 640x640 recommended)remove_profile_picture()- Remove profile picture
newsletter
list_subscribed()- List all subscribed newslettersget_metadata(jid: &Jid)- Get newsletter metadataget_metadata_by_invite(invite: &str)- Get metadata via invite linkcreate(name, description)- Create a new newsletterjoin(jid: &Jid)- Join a newsletterleave(jid: &Jid)- Leave a newsletterupdate(jid, options)- Update newsletter settingssend_reaction(jid, msg_server_id, reaction)- React to a newsletter messageget_messages(jid, count, before)- Fetch newsletter messagessubscribe_live_updates(jid: &Jid)- Subscribe to real-time updates
Newsletter message sending is handled by the unified
client.send_message() method — pass a newsletter JID and the message is sent as plaintext automatically. See the Send API for details.community
create(options: CreateCommunityOptions)- Create a communitydeactivate(jid: &Jid)- Deactivate a communitylink_subgroups(jid: &Jid, subgroups: &[Jid])- Link groups to a communityunlink_subgroups(jid: &Jid, subgroups: &[Jid], remove_orphan_members: bool)- Unlink groups from a communityget_subgroups(jid: &Jid)- List community subgroupsget_subgroup_participant_counts(jid: &Jid)- Get participant counts per subgroupquery_linked_group(community_jid: &Jid, subgroup_jid: &Jid)- Query a linked group’s community metadatajoin_subgroup(community_jid: &Jid, subgroup_jid: &Jid)- Join a community subgroupget_linked_groups_participants(jid: &Jid)- Get participants across linked groups
polls
create(to: &Jid, name: &str, options: &[String], selectable_count: u32)- Create a poll (returns message ID and secret)vote(chat_jid, poll_msg_id, poll_creator_jid, message_secret, option_names)- Cast a vote on a polldecrypt_vote(enc_payload, enc_iv, message_secret, poll_msg_id, poll_creator_jid, voter_jid)- Decrypt a vote (static method)aggregate_votes(poll_options, votes, message_secret, poll_msg_id, poll_creator_jid)- Tally all votes (static method)
media_reupload
request(req: &MediaReuploadRequest)- Request the server to re-upload expired media
server-error receipt and waits up to 30 seconds for a mediaretry notification with an updated download path.
signal
encrypt_message(jid: &Jid, plaintext: &[u8])- Encrypt plaintext for a single recipientdecrypt_message(jid: &Jid, enc_type: EncType, ciphertext: &[u8])- Decrypt a Signal protocol messageencrypt_group_message(group_jid: &Jid, plaintext: &[u8])- Encrypt plaintext for a group using sender keysdecrypt_group_message(group_jid: &Jid, sender_jid: &Jid, ciphertext: &[u8])- Decrypt a group messagevalidate_session(jid: &Jid)- Check whether a Signal session existsdelete_sessions(jids: &[Jid])- Delete Signal sessions and identity keyscreate_participant_nodes(recipient_jids: &[Jid], message: &Message)- Create encrypted participant nodesassert_sessions(jids: &[Jid])- Ensure E2E sessions existget_user_devices(jids: &[Jid])- Get all device JIDs for users
Public fields
http_client
enable_auto_reconnect
true. Set to false to disable auto-reconnect.
auto_reconnect_errors
0 on a successful connection.
custom_enc_handlers
.read().await for lookups and .write().await for modifications. These are also registerable via BotBuilder::with_enc_handler().
RECONNECT_BACKOFF_STEP
reconnect() is called, creating an approximately 5-second offline window before the next connection attempt. This prevents tight reconnect loops after intentional disconnects.
Device State
get_push_name
get_pn
get_lid
get_lid_pn_entry
@s.whatsapp.net) to look up its LID, or a LID JID (@lid) to look up its phone number. Returns None for non-user JIDs (groups, newsletters, etc.) or if no mapping is cached.
The JID to look up — either a PN JID or a LID JID
Contains
lid (String), phone_number (String), created_at (i64 Unix timestamp), and learning_source (LearningSource)This replaces the previous
get_phone_number_from_lid method. The new API accepts a full Jid instead of a raw string and supports bidirectional lookup — pass either a PN or LID JID to resolve the mapping in either direction.LearningSource
TheLearningSource enum indicates how a LID-PN mapping was discovered:
| Variant | Description |
|---|---|
Usync | From a device sync query response |
PeerPnMessage | From an incoming message with sender_lid attribute (sender is PN) |
PeerLidMessage | From an incoming message with sender_pn attribute (sender is LID) |
RecipientLatestLid | From looking up a recipient’s latest LID |
MigrationSyncLatest | From latest history sync migration |
MigrationSyncOld | From old history sync records |
BlocklistActive | From an active blocklist entry |
BlocklistInactive | From an inactive blocklist entry |
Pairing | From device pairing (own JID to LID) |
DeviceNotification | From a device notification with lid attribute |
Other | From an unknown source |
persistence_manager
History Sync
History sync transfers chat history from the phone to the linked device. The client processes history sync notifications through a RAM-optimized pipeline that minimizes peak memory usage.Processing pipeline
When a history sync notification arrives, the client:- Sends a
HistorySyncreceipt immediately (so the phone knows delivery succeeded) - Retrieves the data — either from an inline payload (moved via
.take(), not cloned) or by stream-decrypting an external blob in 8KB chunks - Extracts a
compressed_size_hintfrom the notification’sfile_lengthfield, which the decompressor uses with a 4x multiplier for better buffer pre-allocation (avoids repeatedVecreallocation) - Runs decompression and protobuf parsing on a blocking thread (
tokio::task::spawn_blocking) to avoid stalling the async runtime - Wraps the decompressed blob in a
LazyHistorySyncwith cheap metadata (sync type, chunk order, progress) and dispatches it asEvent::HistorySync(Box<LazyHistorySync>). Full protobuf decoding is deferred until the event handler calls.get()
process_sync_task
MajorSyncTask received from the sync channel returned by Client::new. This is the public entry point for handling history sync and app state sync tasks.
The method dispatches to the appropriate internal handler based on the task variant:
MajorSyncTask::HistorySync— downloads and processes history sync dataMajorSyncTask::AppStateSync— synchronizes app state (contacts, mutes, pins, etc.)
If you use the
Bot builder, sync task processing is handled automatically. You only need this method when building a custom client setup.set_skip_history_sync
skip_history_sync_enabled
true if history sync is currently being skipped.
Offline sync
The client automatically manages offline message sync when reconnecting. During sync, message processing is restricted to sequential mode (1 concurrent task) to preserve ordering.Semaphore transition safety
When offline sync completes, the concurrency semaphore is swapped from 1 permit to 64 permits. Tasks that were already waiting on the old semaphore use a generation-checked re-acquire loop to safely transition — they detect the swap via an atomic generation counter, drop the stale permit, and re-acquire from the new semaphore. This preventspkmsg messages (which carry SKDM for group decryption) from being silently dropped during the transition. See Concurrency gating for details.
Timeout fallback
If the server advertises offline messages but never completes delivery, a 60-second timeout ensures startup is not blocked indefinitely. On timeout:- A warning is logged with the number of processed vs. expected items
- Offline sync is marked complete
OfflineSyncCompletedevent is emitted- Message processing switches from sequential to parallel (64 concurrent tasks)
State reset on reconnect
All offline sync state (counters, timing, concurrency semaphore) is fully reset on reconnect so stale state does not carry over to the next connection. Related events:OfflineSyncPreview, OfflineSyncCompleted
App State
fetch_props
AbPropsCache.
When a stored props hash exists and the cache has been seeded (at least one full fetch has occurred), the request includes the hash for a delta update — the server only returns changed props. Otherwise, a full fetch is performed and all cached props are replaced.
After the response is applied to the cache, the new hash (if present) is persisted for future delta requests.
Features like group privacy token attachment query the AbPropsCache to check whether specific experiment flags are enabled. See AB props cache for details.
AB props cache
The client maintains an in-memoryAbPropsCache that stores server-side A/B experiment properties. The cache is populated each time fetch_props() runs (automatically on connect) and is not persisted — props are re-fetched on every connection.
Features query the cache via client.ab_props().is_enabled(config_code) to check whether a specific experiment flag is active. A prop is considered enabled when its value is "1", "true", or "enabled" (case-insensitive).
Well-known config codes
The following config codes control privacy token behavior on group operations:| Constant | Code | Description |
|---|---|---|
PRIVACY_TOKEN_ON_ALL_1_ON_1_MESSAGES | 10518 | Attach privacy tokens to all 1:1 messages |
PRIVACY_TOKEN_ON_GROUP_CREATE | 11261 | Attach privacy tokens when creating groups |
PRIVACY_TOKEN_ON_GROUP_PARTICIPANT_ADD | 11262 | Attach privacy tokens when adding group participants |
PRIVACY_TOKEN_ONLY_CHECK_LID | 15491 | Only resolve tokens via LID mappings (skip PN fallback) |
wacore::iq::props::config_codes.
The AB props cache is internal to the client. You don’t need to interact with it directly — the library automatically checks relevant flags when performing group operations like
create_group and add_participants.fetch_privacy_settings
set_privacy_setting
Privacy category enum:
Last, Online, Profile, Status, GroupAdd, ReadReceipts, CallAdd, Messages, or DefenseModePrivacy value enum:
All, Contacts, None, ContactBlacklist, MatchLastSeen, Known, Off, or OnStandardset_privacy_disallowed_list
Last, Profile, Status, and GroupAdd.
See Privacy API for details and examples.
set_default_disappearing_mode
Timer duration in seconds. Common values:
86400 (24 hours), 604800 (7 days), 7776000 (90 days). Pass 0 to disable.get_business_profile
None if the account is not a business account or has no business profile.
JID of the business account to query
clean_dirty_bits
DirtyBit struct contains a dirty_type (e.g., AccountSync, Groups, SyncdAppState, NewsletterMetadata) and an optional timestamp.
Protocol Operations
send_node
Binary protocol node to send
ClientError::NotConnected- Not connectedClientError::EncryptSend- Encryption/send failure
send_raw_bytes
wacore_binary::marshal::marshal_to). Sending malformed data will cause the server to close the connection.
WABinary-marshaled stanza bytes
ClientError::NotConnected- Not connectedClientError::EncryptSend- Encryption/send failure
generate_message_id
This is intended for advanced users who need to build custom protocol interactions or manage message IDs manually. Most users should use
send_message which handles ID generation automatically.send_iq
IQ query containing stanza type, namespace, content, and optional timeout
execute
A typed IQ specification that defines the request structure and response parsing
wait_for_node
Filter specifying which node to wait for (by tag and attributes)
Register the waiter before performing the action that triggers the expected node. When no waiters are active, this has zero cost (single atomic load per incoming node).
NodeFilter
Builder for matching incoming protocol nodes:wait_for_sent_node
wait_for_node.
Filter specifying which outgoing node to intercept (by tag and attributes)
Register the waiter before performing the action that produces the outgoing node. When no sent-node waiters are active, this has zero cost (single atomic load per outgoing node). Useful for testing whether
<tctoken> or <cstoken> was attached to a sent stanza.register_handler
Handler implementing the EventHandler trait
ChannelEventHandler:
register_chatstate_handler
Arc for thread-safe sharing across the event dispatching system.
set_raw_node_forwarding
Event::RawNode is emitted for every decoded stanza before the stanza router dispatches it. Disabled by default to avoid overhead.
Whether to emit
Event::RawNode for every incoming stanzaCall management
reject_call
<call><reject> stanza to the WhatsApp server.
The ID of the incoming call to reject. Must not be empty.
The JID of the caller.
Spam Reporting
send_spam_report
The spam report request containing:
message_id- ID of the message being reportedmessage_timestamp- Timestamp of the messagespam_flow- Context where report was initiated (MessageMenu, GroupInfoReport, etc.)from_jid- Optional sender JIDgroup_jid- Optional group JID for group spamgroup_subject- Optional group name/subject for group reportsparticipant_jid- Optional participant JID in group contextraw_message- Optional raw message bytesmedia_type- Optional media type if reporting medialocal_message_type- Optional local message type
SpamReportResult indicating success or failure
Example:
MessageMenu- Reported from message context menuGroupInfoReport- Reported from group info screenGroupSpamBannerReport- Reported from group spam bannerContactInfo- Reported from contact info screenStatusReport- Reported from status view
Passive Mode
set_passive
false (active), the server starts sending offline messages.
Prekeys
refresh_pre_keys
InMemoryBackend) and the server may still hold pre-key IDs whose private key material you cannot reconstruct.
Any pkmsg referencing those old IDs will fail permanently with InvalidPreKeyId. Calling refresh_pre_keys() uploads a fresh batch that the caller does have locally, and old unmatched IDs drain as peers consume them.
Behavior:
- Acquires the internal
prekey_upload_lockso this force-upload cannot race with the count-based and digest-repair upload paths - Uploads a full batch of 812 pre-keys with Fibonacci retry backoff (1s, 2s, 3s, 5s, 8s, … capped at 610s)
- Retries until success or the connection is lost
send_digest_key_bundle
WAWebDigestKeyJob.digestKey() flow.
Behavior:
- Queries the server for the current key bundle digest (identity key, signed pre-key, pre-key IDs, and a SHA-1 hash)
- If the server returns 404 (no record), triggers a full pre-key re-upload
- On success, loads local keys, computes the same SHA-1 digest, and compares
- Hash mismatches or missing keys are logged but do not trigger re-upload — only 404 does
Memory diagnostics
Requires the
debug-diagnostics feature flag.memory_diagnostics
MemoryDiagnostics fields:
| Field | Type | Description |
|---|---|---|
group_cache | u64 | Moka cache — group metadata |
device_registry_cache | u64 | Moka cache — device registry |
lid_pn_lid_entries | u64 | LID-to-PN mapping entries |
lid_pn_pn_entries | u64 | PN-to-LID mapping entries |
retried_group_messages | u64 | Pending group message retries |
recent_messages | u64 | Recent message dedup cache |
message_retry_counts | u64 | Message retry counter cache |
pdo_pending_requests | u64 | PDO pending request cache |
sender_key_device_cache | u64 | Per-group sender key device tracking cache |
session_locks | u64 | Per-device session locks |
chat_lanes | u64 | Per-chat lanes (combined enqueue lock + message queue) |
response_waiters | usize | Active IQ response waiters |
node_waiters | usize | Active node waiters |
pending_retries | usize | Pending message retries |
presence_subscriptions | usize | Active presence subscriptions |
app_state_key_requests | usize | Pending app state key requests |
app_state_syncing | usize | Active app state sync operations |
signal_cache_sessions | usize | Cached Signal sessions |
signal_cache_identities | usize | Cached Signal identities |
signal_cache_sender_keys | usize | Cached sender keys |
chatstate_handlers | usize | Registered chat state handlers |
custom_enc_handlers | usize | Registered custom encryption handlers |
Error Types
See Also
- Bot - High-level builder with event handlers
- Events - Event system and types
- Sending Messages - Sending and receiving messages
- Group Management - Working with groups