Documentation Index Fetch the complete documentation index at: https://mintlify.com/jlucaso1/whatsapp-rust/llms.txt
Use this file to discover all available pages before exploring further.
Overview
WhatsApp-Rust uses an event-driven architecture where the client emits events for all WhatsApp protocol interactions. Your application subscribes to these events to handle messages, connection changes, and notifications.
Event System Architecture
CoreEventBus
Location: wacore/src/types/events.rs
#[derive( Default , Clone )]
pub struct CoreEventBus {
handlers : Arc < RwLock < Vec < Arc < dyn EventHandler >>>>,
}
impl CoreEventBus {
pub fn dispatch ( & self , event : & Event ) {
for handler in self . handlers . read () . expect ( "..." ) . iter () {
handler . handle_event ( event );
}
}
pub fn has_handlers ( & self ) -> bool {
! self . handlers . read () . expect ( "..." ) . is_empty ()
}
}
Features:
Thread-safe event dispatching
Multiple handlers supported
Clone-cheap with Arc
EventHandler Trait
pub trait EventHandler : Send + Sync {
fn handle_event ( & self , event : & Event );
}
Implementation:
struct MyHandler ;
impl EventHandler for MyHandler {
fn handle_event ( & self , event : & Event ) {
match event {
Event :: Message ( msg , info ) => {
println! ( "Message from {}: {:?}" , info . source . sender, msg );
}
_ => {}
}
}
}
client . core . event_bus . add_handler ( Arc :: new ( MyHandler ));
Event Enum
Location: wacore/src/types/events.rs:292-351
#[derive( Debug , Clone , Serialize )]
pub enum Event {
// Connection
Connected ( Connected ),
Disconnected ( Disconnected ),
StreamReplaced ( StreamReplaced ),
StreamError ( StreamError ),
ConnectFailure ( ConnectFailure ),
TemporaryBan ( TemporaryBan ),
// Pairing
PairingQrCode { code : String , timeout : Duration },
PairingCode { code : String , timeout : Duration },
PairSuccess ( PairSuccess ),
PairError ( PairError ),
QrScannedWithoutMultidevice ( QrScannedWithoutMultidevice ),
ClientOutdated ( ClientOutdated ),
LoggedOut ( LoggedOut ),
// Messages
Message ( Box < wa :: Message >, MessageInfo ),
Receipt ( Receipt ),
UndecryptableMessage ( UndecryptableMessage ),
Notification ( Node ),
// Presence
ChatPresence ( ChatPresenceUpdate ),
Presence ( PresenceUpdate ),
// User Updates
PictureUpdate ( PictureUpdate ),
UserAboutUpdate ( UserAboutUpdate ),
PushNameUpdate ( PushNameUpdate ),
SelfPushNameUpdated ( SelfPushNameUpdated ),
// Group Updates
JoinedGroup ( LazyConversation ),
GroupInfoUpdate { jid : Jid , update : Box < wa :: SyncActionValue > },
// Contact Updates
ContactUpdate ( ContactUpdate ),
// Chat State
PinUpdate ( PinUpdate ),
MuteUpdate ( MuteUpdate ),
ArchiveUpdate ( ArchiveUpdate ),
MarkChatAsReadUpdate ( MarkChatAsReadUpdate ),
// History Sync
HistorySync ( HistorySync ),
OfflineSyncPreview ( OfflineSyncPreview ),
OfflineSyncCompleted ( OfflineSyncCompleted ),
// Device Updates
DeviceListUpdate ( DeviceListUpdate ),
BusinessStatusUpdate ( BusinessStatusUpdate ),
}
Connection Events
Connected
Emitted: After successful connection and authentication
#[derive( Debug , Clone , Serialize )]
pub struct Connected ;
Event :: Connected ( Connected )
Usage:
Event :: Connected ( _ ) => {
println! ( "✅ Connected to WhatsApp" );
// Safe to send messages now
}
Disconnected
Emitted: When connection is lost
#[derive( Debug , Clone , Serialize )]
pub struct Disconnected ;
Event :: Disconnected ( Disconnected )
Behavior: Client automatically attempts reconnection
ConnectFailure
Emitted: When connection fails with a specific reason
#[derive( Debug , Clone , Serialize )]
pub struct ConnectFailure {
pub reason : ConnectFailureReason ,
pub message : String ,
pub raw : Option < Node >,
}
#[derive( Debug , Clone , PartialEq , Eq , Copy , Serialize )]
pub enum ConnectFailureReason {
Generic , // 400
LoggedOut , // 401
TempBanned , // 402
MainDeviceGone , // 403
UnknownLogout , // 406
ClientOutdated , // 405
BadUserAgent , // 409
CatExpired , // 413
CatInvalid , // 414
NotFound , // 415
ClientUnknown , // 418
InternalServerError , // 500
Experimental , // 501
ServiceUnavailable , // 503
Unknown ( i32 ),
}
Helper methods:
if reason . is_logged_out () {
// Clear session and re-pair
}
if reason . should_reconnect () {
// Retry connection
}
TemporaryBan
Emitted: When account is temporarily banned
#[derive( Debug , Clone , Serialize )]
pub struct TemporaryBan {
pub code : TempBanReason ,
pub expire : Duration ,
}
#[derive( Debug , Clone , PartialEq , Eq , Serialize )]
pub enum TempBanReason {
SentToTooManyPeople , // 101
BlockedByUsers , // 102
CreatedTooManyGroups , // 103
SentTooManySameMessage , // 104
BroadcastList , // 106
Unknown ( i32 ),
}
Usage:
Event :: TemporaryBan ( ban ) => {
eprintln! ( "Banned: {} (expires in {:?})" , ban . code, ban . expire);
}
StreamReplaced
Emitted: When another device connects with the same credentials
Event :: StreamReplaced ( _ ) => {
println! ( "⚠️ Another instance connected - disconnecting" );
}
Pairing Events
PairingQrCode
Emitted: For each QR code in rotation
Event :: PairingQrCode {
code : String , // ASCII art QR or data string
timeout : Duration , // 60s first, 20s subsequent
}
Example:
Event :: PairingQrCode { code , timeout } => {
println! ( "Scan this QR (valid {}s):" , timeout . as_secs ());
println! ( "{}" , code );
}
PairingCode
Emitted: When pair code is generated
Event :: PairingCode {
code : String , // 8-character code
timeout : Duration , // ~180 seconds
}
Example:
Event :: PairingCode { code , .. } => {
println! ( "Enter {} on your phone" , code );
}
PairSuccess
Emitted: When pairing completes successfully
#[derive( Debug , Clone , Serialize )]
pub struct PairSuccess {
pub id : Jid ,
pub lid : Jid ,
pub business_name : String ,
pub platform : String ,
}
Example:
Event :: PairSuccess ( info ) => {
println! ( "✅ Paired as {}" , info . id);
println! ( "LID: {}" , info . lid);
println! ( "Name: {}" , info . business_name);
}
PairError
Emitted: When pairing fails
#[derive( Debug , Clone , Serialize )]
pub struct PairError {
pub id : Jid ,
pub lid : Jid ,
pub business_name : String ,
pub platform : String ,
pub error : String ,
}
Message Events
Message
Emitted: For all incoming messages (text, media, etc.)
Event :: Message ( Box < wa :: Message >, MessageInfo )
MessageInfo structure:
#[derive( Debug , Clone , Serialize )]
pub struct MessageInfo {
pub id : MessageId ,
pub source : MessageSource ,
pub timestamp : DateTime < Utc >,
pub push_name : String ,
pub is_group : bool ,
pub category : String ,
}
#[derive( Debug , Clone , Serialize )]
pub struct MessageSource {
pub sender : Jid , // Who sent the message
pub chat : Jid , // Where it was sent (group or DM)
pub is_from_me : bool ,
pub is_bot : bool ,
}
Example:
use waproto :: whatsapp as wa;
Event :: Message ( msg , info ) => {
println! ( "From: {} in {}" , info . source . sender, info . source . chat);
// Text message
if let Some ( text ) = & msg . conversation {
println! ( "Text: {}" , text );
}
// Extended text (with link preview, quoted message, etc.)
if let Some ( ext ) = & msg . extended_text_message {
println! ( "Text: {}" , ext . text . as_deref () . unwrap_or ( "" ));
if let Some ( context ) = & ext . context_info {
if let Some ( quoted ) = & context . quoted_message {
println! ( "Quoted: {:?}" , quoted );
}
}
}
// Image message
if let Some ( img ) = & msg . image_message {
println! ( "Image: {} ({}x{})" ,
img . caption . as_deref () . unwrap_or ( "" ),
img . width . unwrap_or ( 0 ),
img . height . unwrap_or ( 0 )
);
}
// Video, audio, document, sticker, etc.
// See waproto::whatsapp::Message for all types
}
Receipt
Emitted: For delivery/read/played receipts
#[derive( Debug , Clone , Serialize )]
pub struct Receipt {
pub source : MessageSource ,
pub message_ids : Vec < MessageId >,
pub timestamp : DateTime < Utc >,
pub r#type : ReceiptType ,
pub message_sender : Jid ,
}
#[derive( Debug , Clone , Copy , PartialEq , Eq , Serialize )]
pub enum ReceiptType {
Delivery ,
Read ,
ReadSelf ,
Played ,
Sender ,
Retry ,
ServerError ,
Inactive ,
}
Example:
Event :: Receipt ( receipt ) => {
match receipt . r# type {
ReceiptType :: Read => {
println! ( "✓✓ Read by {}" , receipt . source . sender);
}
ReceiptType :: Delivery => {
println! ( "✓ Delivered to {}" , receipt . source . sender);
}
_ => {}
}
}
UndecryptableMessage
Emitted: When a message cannot be decrypted
#[derive( Debug , Clone , Serialize )]
pub struct UndecryptableMessage {
pub info : MessageInfo ,
pub is_unavailable : bool ,
pub unavailable_type : UnavailableType ,
pub decrypt_fail_mode : DecryptFailMode ,
}
#[derive( Debug , Clone , PartialEq , Eq , Serialize )]
pub enum UnavailableType {
Unknown ,
ViewOnce , // View-once media already viewed
}
#[derive( Debug , Clone , Copy , PartialEq , Eq , Serialize )]
pub enum DecryptFailMode {
Show , // Show placeholder in chat
Hide , // Hide from chat
}
Example:
Event :: UndecryptableMessage ( undec ) => {
if matches! ( undec . unavailable_type, UnavailableType :: ViewOnce ) {
println! ( "View-once message already consumed" );
} else {
eprintln! ( "Failed to decrypt message from {}" , undec . info . source . sender);
}
}
Presence Events
ChatPresence
Emitted: For typing indicators and recording states
#[derive( Debug , Clone , Serialize )]
pub struct ChatPresenceUpdate {
pub source : MessageSource ,
pub state : ChatPresence ,
pub media : ChatPresenceMedia ,
}
#[derive( Debug , Clone , Copy , PartialEq , Eq , Serialize )]
pub enum ChatPresence {
Composing ,
Paused ,
}
#[derive( Debug , Clone , Copy , PartialEq , Eq , Serialize )]
pub enum ChatPresenceMedia {
Text ,
Audio ,
}
Example:
Event :: ChatPresence ( update ) => {
match ( update . state, update . media) {
( ChatPresence :: Composing , ChatPresenceMedia :: Text ) => {
println! ( "{} is typing..." , update . source . sender);
}
( ChatPresence :: Composing , ChatPresenceMedia :: Audio ) => {
println! ( "{} is recording audio..." , update . source . sender);
}
( ChatPresence :: Paused , _ ) => {
println! ( "{} stopped typing" , update . source . sender);
}
}
}
Presence
Emitted: For online/offline status and last seen
#[derive( Debug , Clone , Serialize )]
pub struct PresenceUpdate {
pub from : Jid ,
pub unavailable : bool ,
pub last_seen : Option < DateTime < Utc >>,
}
Example:
Event :: Presence ( update ) => {
if update . unavailable {
println! ( "{} is offline" , update . from);
if let Some ( last_seen ) = update . last_seen {
println! ( "Last seen: {}" , last_seen );
}
} else {
println! ( "{} is online" , update . from);
}
}
User Update Events
PictureUpdate
Emitted: When a user changes their profile picture
#[derive( Debug , Clone , Serialize )]
pub struct PictureUpdate {
pub jid : Jid ,
pub author : Jid ,
pub timestamp : DateTime < Utc >,
pub photo_change : Option < wa :: PhotoChange >,
}
UserAboutUpdate
Emitted: When a user changes their status/about
#[derive( Debug , Clone , Serialize )]
pub struct UserAboutUpdate {
pub jid : Jid ,
pub status : String ,
pub timestamp : DateTime < Utc >,
}
PushNameUpdate
Emitted: When a contact changes their display name
#[derive( Debug , Clone , Serialize )]
pub struct PushNameUpdate {
pub jid : Jid ,
pub message : Box < MessageInfo >,
pub old_push_name : String ,
pub new_push_name : String ,
}
SelfPushNameUpdated
Emitted: When your own push name is updated
#[derive( Debug , Clone , Serialize )]
pub struct SelfPushNameUpdated {
pub from_server : bool ,
pub old_name : String ,
pub new_name : String ,
}
Group Events
JoinedGroup
Emitted: When added to a group
Event :: JoinedGroup ( LazyConversation )
LazyConversation: Lazily-parsed group conversation data
Event :: JoinedGroup ( lazy_conv ) => {
if let Some ( conv ) = lazy_conv . get () {
println! ( "Joined group: {}" , conv . id);
}
}
GroupInfoUpdate
Emitted: When group metadata changes (subject, participants, etc.)
Event :: GroupInfoUpdate {
jid : Jid ,
update : Box < wa :: SyncActionValue >,
}
Example:
Event :: GroupInfoUpdate { jid , update } => {
if let Some ( action ) = & update . group_action {
println! ( "Group {} updated: {:?}" , jid , action );
}
}
Chat State Events
PinUpdate
Emitted: When a chat is pinned/unpinned
#[derive( Debug , Clone , Serialize )]
pub struct PinUpdate {
pub jid : Jid ,
pub timestamp : DateTime < Utc >,
pub action : Box < wa :: sync_action_value :: PinAction >,
pub from_full_sync : bool ,
}
MuteUpdate
Emitted: When a chat is muted/unmuted
#[derive( Debug , Clone , Serialize )]
pub struct MuteUpdate {
pub jid : Jid ,
pub timestamp : DateTime < Utc >,
pub action : Box < wa :: sync_action_value :: MuteAction >,
pub from_full_sync : bool ,
}
ArchiveUpdate
Emitted: When a chat is archived/unarchived
#[derive( Debug , Clone , Serialize )]
pub struct ArchiveUpdate {
pub jid : Jid ,
pub timestamp : DateTime < Utc >,
pub action : Box < wa :: sync_action_value :: ArchiveChatAction >,
pub from_full_sync : bool ,
}
MarkChatAsReadUpdate
Emitted: When a chat is marked as read
#[derive( Debug , Clone , Serialize )]
pub struct MarkChatAsReadUpdate {
pub jid : Jid ,
pub timestamp : DateTime < Utc >,
pub action : Box < wa :: sync_action_value :: MarkChatAsReadAction >,
pub from_full_sync : bool ,
}
History Sync Events
HistorySync
Emitted: For chat history synchronization
Event :: HistorySync ( HistorySync )
HistorySync: Protobuf message containing chat history
OfflineSyncPreview
Emitted: Preview of pending offline sync data
#[derive( Debug , Clone , Serialize )]
pub struct OfflineSyncPreview {
pub total : i32 ,
pub app_data_changes : i32 ,
pub messages : i32 ,
pub notifications : i32 ,
pub receipts : i32 ,
}
OfflineSyncCompleted
Emitted: When offline sync completes
#[derive( Debug , Clone , Serialize )]
pub struct OfflineSyncCompleted {
pub count : i32 ,
}
Device Events
DeviceListUpdate
Emitted: When a user’s device list changes
#[derive( Debug , Clone , Serialize )]
pub struct DeviceListUpdate {
pub user : Jid ,
pub lid_user : Option < Jid >,
pub update_type : DeviceListUpdateType ,
pub devices : Vec < DeviceNotificationInfo >,
pub key_index : Option < KeyIndexInfo >,
pub contact_hash : Option < String >,
}
#[derive( Debug , Clone , Copy , PartialEq , Eq , Serialize )]
pub enum DeviceListUpdateType {
Add ,
Remove ,
Update ,
}
BusinessStatusUpdate
Emitted: When a business account status changes
#[derive( Debug , Clone , Serialize )]
pub struct BusinessStatusUpdate {
pub jid : Jid ,
pub update_type : BusinessUpdateType ,
pub timestamp : i64 ,
pub target_jid : Option < Jid >,
pub hash : Option < String >,
pub verified_name : Option < String >,
pub product_ids : Vec < String >,
pub collection_ids : Vec < String >,
pub subscriptions : Vec < BusinessSubscription >,
}
#[derive( Debug , Clone , Copy , PartialEq , Eq , Serialize )]
pub enum BusinessUpdateType {
RemovedAsBusiness ,
VerifiedNameChanged ,
ProfileUpdated ,
ProductsUpdated ,
CollectionsUpdated ,
SubscriptionsUpdated ,
Unknown ,
}
Event Handler Patterns
Bot Builder Pattern
use whatsapp_rust :: bot :: Bot ;
use wacore :: types :: events :: Event ;
let mut bot = Bot :: builder ()
. with_backend ( backend )
. on_event ( | event , client | async move {
match event {
Event :: Message ( msg , info ) => {
// Handle message
}
Event :: Connected ( _ ) => {
// Handle connection
}
_ => {}
}
})
. build ()
. await ? ;
Multiple Handlers
struct MessageHandler ;
impl EventHandler for MessageHandler {
fn handle_event ( & self , event : & Event ) {
if let Event :: Message ( msg , info ) = event {
// Handle messages
}
}
}
struct ConnectionHandler ;
impl EventHandler for ConnectionHandler {
fn handle_event ( & self , event : & Event ) {
match event {
Event :: Connected ( _ ) => { /* ... */ }
Event :: Disconnected ( _ ) => { /* ... */ }
_ => {}
}
}
}
client . core . event_bus . add_handler ( Arc :: new ( MessageHandler ));
client . core . event_bus . add_handler ( Arc :: new ( ConnectionHandler ));
Async Event Handlers
use tokio :: sync :: mpsc;
let ( tx , mut rx ) = mpsc :: unbounded_channel ();
struct AsyncHandler {
tx : mpsc :: UnboundedSender < Event >,
}
impl EventHandler for AsyncHandler {
fn handle_event ( & self , event : & Event ) {
let _ = self . tx . send ( event . clone ());
}
}
client . core . event_bus . add_handler ( Arc :: new ( AsyncHandler { tx }));
// Process events asynchronously
tokio :: spawn ( async move {
while let Some ( event ) = rx . recv () . await {
// Async processing
}
});
LazyConversation
Purpose: Avoid parsing large protobuf messages unless needed
// wacore/src/types/events.rs:42-97
pub struct LazyConversation {
raw_bytes : Bytes , // Zero-copy bytes
parsed : Arc < OnceLock < wa :: Conversation >>, // Parse once
}
impl LazyConversation {
pub fn get ( & self ) -> Option < & wa :: Conversation > {
// Parse on first access
let conv = self . parsed . get_or_init ( ||
wa :: Conversation :: decode ( & self . raw_bytes[ .. ]) . unwrap_or_default ()
);
if conv . id . is_empty () { None } else { Some ( conv ) }
}
}
Usage:
Event :: JoinedGroup ( lazy_conv ) => {
// No parsing cost unless you access it
if interested_in_group () {
if let Some ( conv ) = lazy_conv . get () {
// Parse happens here
process_group ( conv );
}
}
}
SharedData
Purpose: Cheap cloning of large event data
// wacore/src/types/events.rs:14-39
pub struct SharedData < T >( pub Arc < T >);
impl < T > std :: ops :: Deref for SharedData < T > {
type Target = T ;
fn deref ( & self ) -> & Self :: Target {
& self . 0
}
}
Usage:
let shared = SharedData :: new ( expensive_data );
let clone1 = shared . clone (); // O(1) - just increments Arc counter
let clone2 = shared . clone (); // O(1)
Best Practices
Event Filtering
. on_event ( | event , client | async move {
// Only handle events you care about
match event {
Event :: Message ( msg , info ) if ! info . source . is_from_me => {
// Only handle messages from others
}
Event :: Message ( msg , info ) if info . is_group => {
// Only handle group messages
}
_ => {}
}
})
Error Handling
. on_event ( | event , client | async move {
if let Err ( e ) = handle_event ( event , client ) . await {
eprintln! ( "Event handler error: {}" , e );
}
})
async fn handle_event ( event : & Event , client : Arc < Client >) -> Result <()> {
match event {
Event :: Message ( msg , info ) => {
process_message ( msg , info , client ) . await ?
}
_ => {}
}
Ok (())
}
Spawning Tasks
. on_event ( | event , client | async move {
match event {
Event :: Message ( msg , info ) => {
let client = client . clone ();
let msg = msg . clone ();
let info = info . clone ();
tokio :: spawn ( async move {
// Process in background
process_message ( & msg , & info , & client ) . await ;
});
}
_ => {}
}
})
Architecture Understand the event bus system
Authentication Learn about pairing events
Sending messages Sending and receiving messages
Client API Complete client API reference