Skip to main content
The Signal struct provides direct access to Signal protocol operations including message encryption/decryption for both 1:1 and group conversations, session management, and participant node creation.
These are low-level APIs that bypass the high-level message sending pipeline. Most users should use client.send_message() which handles encryption automatically. Use these methods only when you need direct control over the Signal protocol layer.

Access

Access Signal protocol operations through the client:

Methods

encrypt_message

Encrypt plaintext for a single recipient using the Signal protocol.
Parameters:
  • jid - Recipient JID. PN JIDs are resolved to LID and Hosted JIDs to HostedLid when a mapping exists, matching WA Web’s SignalAddress.toString() and the internal send path. See Signal address resolution.
  • plaintext - Raw bytes to encrypt. The caller is responsible for padding if needed.
Returns:
  • (EncType, Vec<u8>) - The encryption type and ciphertext bytes
EncType variants:
  • EncType::PreKeyMessage - Session was just established (includes prekey bundle)
  • EncType::Message - Standard encrypted message
Example:

decrypt_message

Decrypt a Signal protocol message from a sender.
Parameters:
  • jid - Sender JID. PN JIDs are resolved to LID and Hosted JIDs to HostedLid when a mapping exists.
  • enc_type - The encryption type (EncType::PreKeyMessage or EncType::Message)
  • ciphertext - Encrypted bytes to decrypt
Returns:
  • Vec<u8> - Raw padded plaintext. Use MessageUtils::unpad_message_ref with the stanza’s v attribute if WhatsApp message unpadding is needed.
Passing EncType::SenderKey returns an error — use decrypt_group_message for sender-key encrypted group messages.
Example:

encrypt_group_message

Encrypt plaintext for a group using sender keys.
Parameters:
  • group_jid - Group JID (@g.us)
  • plaintext - Raw bytes to encrypt
Returns:
  • (Option<Vec<u8>>, Vec<u8>) - A tuple of optional SKDM bytes and ciphertext bytes. The SKDM is Some only when a new sender key was created (first encrypt for this group or after key rotation). You must distribute the SKDM to all group participants when present.
Concurrent calls are serialized on a per-(group_jid, sender_jid) chain lock (sender_key_lock), keyed here by your own JID as the sender. Two overlapping encrypt_group_message calls for the same group safely queue behind one another. A concurrent decrypt_group_message call only shares this lock when its sender_jid is your own JID (an unusual case) — decrypting messages from other participants uses a different chain and runs fully in parallel. See sender-key chain locking.
Example:

decrypt_group_message

Decrypt a group (sender-key) message.
Parameters:
  • group_jid - Group JID
  • sender_jid - Sender’s JID within the group
  • ciphertext - Encrypted bytes to decrypt
Returns:
  • Vec<u8> - Raw padded plaintext. Use MessageUtils::unpad_message_ref with the stanza’s v attribute if WhatsApp message unpadding is needed.
Concurrent calls are serialized on a per-(group_jid, sender_jid) chain lock (sender_key_lock), so two overlapping decrypt_group_message calls for the same sender safely queue behind one another. Calls for different senders in the same group — or a concurrent encrypt_group_message call, which uses your own JID as the chain identity — touch a different chain and run in parallel. See sender-key chain lock (group receive).
Example:

sender_key_distribution

Create (or lazily initialize) and serialize the current outgoing sender-key distribution message for a group.
Parameters:
  • group_jid - Group JID
  • sender_jid - Your own JID as it should appear to other group members (the sender key chain owner)
Returns:
  • Vec<u8> - Serialized SenderKeyDistributionMessage bytes, ready to send to a new or existing group member (e.g. when adding a participant who needs to decrypt future messages)
This is the same distribution payload encrypt_group_message returns automatically on first use — call it directly when you need to (re)distribute a sender key out of band, such as when a new member joins and needs the current chain without waiting for the next group message. The distribution is durably persisted before this method returns. Example:

process_sender_key_distribution

Process an incoming sender-key distribution message for a group, installing the sender’s chain so future skmsg stanzas from them can be decrypted.
Parameters:
  • group_jid - Group JID
  • sender_jid - JID of the participant who distributed the sender key
  • distribution - Serialized SenderKeyDistributionMessage bytes received from the sender (typically extracted from an incoming SKDM node)
The sender-key chain is durably persisted before this method returns. Example:

has_sender_key

Check whether sender-key state already exists for a group and sender.
Parameters:
  • group_jid - Group JID
  • sender_jid - Sender’s JID within the group
Returns:
  • bool - true if a sender-key chain is already stored for this (group_jid, sender_jid) pair
Example:

delete_sender_key

Durably delete a sender-key chain for a group and sender, e.g. on group exit or key rotation.
Parameters:
  • group_jid - Group JID
  • sender_jid - Sender’s JID within the group
The deletion waits for any in-flight chain mutation (e.g. a concurrent encrypt_group_message ratchet advance) to finish before removing the chain, and is flushed to the persistent backend before returning. Example:

validate_session

Check whether a Signal session exists for a JID.
Parameters:
  • jid - JID to check. PN JIDs are resolved to LID and Hosted JIDs to HostedLid when a mapping exists.
Returns:
  • bool - true if a session exists, false otherwise
Example:

session_info

Inspect an existing pairwise Signal session, migrating legacy PN-addressed state to its resolved LID namespace when needed.
Parameters:
  • jid - JID to inspect. PN JIDs are resolved to LID and Hosted JIDs to HostedLid when a mapping exists.
Returns:
  • Option<SignalSessionInfo> - Some with the session’s base key and remote registration id if a session exists, None otherwise
If only a legacy PN-addressed session exists and the resolved address is a LID, this method migrates it first (moving session and identity state to the LID namespace) and then reports on the migrated session — mirroring the on-the-fly migration used by the decrypt path. This means session_info is not a purely read-only probe: it can itself perform the migration, so a subsequent migrate_sessions call on the same pair may find there is nothing left to move. See SignalSessionInfo and PN→LID session migration.
Example:

delete_sessions

Delete Signal sessions and identity keys for the given JIDs.
Parameters:
  • jids - JIDs whose sessions and identity keys should be deleted. PN JIDs are resolved to LID and Hosted JIDs to HostedLid when a mapping exists.
This matches WhatsApp Web’s deleteRemoteSession behavior, which removes both the session and identity key as a paired operation. Changes are flushed to the persistent backend before returning. Example:

install_prekey_bundle

Durably install a supplied pre-key bundle for a JID, establishing (or replacing) a pairwise session from it.
Parameters:
  • jid - JID to install the session for. Resolved the same way as encrypt_message.
  • bundle - A PreKeyBundle obtained out of band — e.g. from a manual/custom prekey fetch — rather than through assert_sessions’s normal usync + fetch flow
Returns:
  • IdentityChange - IdentityChange::NewOrUnchanged if the peer had no identity key or it matched, or IdentityChange::ReplacedExisting if this bundle’s identity key replaced a previously trusted one
The session is durably persisted before this method returns. Example:

migrate_sessions

Move pairwise session and identity state from one JID namespace to another for the same underlying account (PN→LID, or Hosted→HostedLid).
Parameters:
  • from - Source JID namespace (must be Pn or Hosted)
  • to - Destination JID namespace (must be Lid for a Pn source, or HostedLid for a Hosted source)
Returns:
  • SignalSessionMigration - Counts of sessions and identities moved, discarded, or skipped. See SignalSessionMigration.
Scans known device slots under from, moving each pairwise session and identity to to when the destination doesn’t already have one, and discarding the stale source entry when it does. This is the same logic the client runs automatically on LID discovery and on-the-fly during decryption — exposed here for callers that want to trigger it manually. See PN→LID session migration. Mismatched namespace pairs (e.g. a Pn source with a HostedLid destination, or two otherwise unrelated JIDs) are rejected with SignalError::InvalidInput. Example:

create_participant_nodes

Create encrypted participant <to> nodes for the given recipient JIDs.
Parameters:
  • recipient_jids - JIDs to encrypt for
  • message - Protobuf message to encrypt
Returns:
  • (Vec<Node>, bool) - The encrypted participant XML nodes and a boolean indicating whether a device identity node should be included in the stanza (true when any participant received a PreKey message).
This method resolves devices, ensures Signal sessions exist, encrypts the message for each device, and returns the resulting XML nodes. It acquires session locks matching the DM send path via session_mutexes_for() (bare recipient JID for the recipient, per-device for own companion devices). Example:

assert_sessions

Ensure E2E sessions exist for the given JIDs.
Parameters:
  • jids - JIDs to ensure sessions for
If sessions do not exist, this method fetches prekey bundles from the server and establishes new sessions. Example:

get_user_devices

Get all known device JIDs for the given user JIDs via usync.
Parameters:
  • jids - User JIDs to query
Returns:
  • Vec<Jid> - All device JIDs for the given users
Example:

EncType

The EncType enum represents the Signal protocol encryption type used for a message:
EncType exposes two predicate helpers: is_session() (true for Message / PreKeyMessage, excludes MessageSecret) and is_bot_secret() (true only for MessageSecret).
EncType is re-exported from the crate root as whatsapp_rust::EncType — prefer that over the internal wacore::message_processing::EncType path shown in older examples.

SignalSessionInfo

Read-only information from a currently open pairwise session, returned by session_info:
SignalSessionInfo is re-exported from the crate root, so it’s also available as whatsapp_rust::SignalSessionInfo.

SignalSessionMigration

Result of moving pairwise session state between address namespaces, returned by migrate_sessions:
Methods:
  • has_state_changes(self) -> bool - true if any source state was moved or removed (migrated != 0 || migrated_identities != 0 || discarded_identities != 0). Useful for deciding whether a migration was a meaningful no-op (nothing to move) versus one that changed persisted state.
SignalSessionMigration is #[non_exhaustive] and re-exported from the crate root as whatsapp_rust::SignalSessionMigration.

Bot message decryption (msmsg)

When you message Meta AI or another @bot account, the bot’s replies arrive as <enc type="msmsg"> stanzas. These are not Signal-session encrypted — they use a dual-HKDF derivation over the 32-byte messageSecret from the prompt you sent, then AES-256-GCM. The client handles this end to end and transparently:
  1. On send to a bot, the outbound MessageContextInfo.messageSecret is persisted (keyed by (chat, sender, msg_id)) so the reply can be decrypted later.
  2. On receive, an msmsg stanza is decrypted and decoded into a wa::Message, then dispatched as a normal Event::Messages — there is no separate bot event. The sender is the bot JID (e.g. …@bot) and MsgMetaInfo.target_id points back at your original prompt.
  3. On failure (missing secret, GCM tag mismatch, malformed proto) the client nacks with reason 495 (MissingMessageSecret) instead of silently dropping, and group bot replies are acked with a bare <ack class="message"> matching WA Web.
You don’t need to call anything — receiving bot replies works as soon as you’ve sent a message to the bot from the same client. The low-level primitive is wacore::bot_message::decrypt_bot_message(message_secret, enc_iv, enc_payload, ctx), and persistence is backed by the MsgSecretStore trait.

Usage examples

Manual 1:1 encryption round-trip

Check session before sending

Group encryption with SKDM handling

Add a participant to an existing sender-key group

Reset a broken session

Manually migrate a session to LID addressing

Error types

SignalError

All signal methods return Result<T, SignalError>:
Variants:
  • Protocol — Signal protocol error (session mismatch, decode failure, etc.)
  • Unsupported — Operation not supported for the given parameters
  • InvalidInput — The operation is supported but one of its inputs is malformed — e.g. a sender-key distribution message that fails to decode, or a migrate_sessions call with a source/destination pair that isn’t a valid PN→LID or Hosted→HostedLid namespace match
  • Internal — Catch-all for other errors

See also