Skip to main content
This guide will help you create a simple WhatsApp bot that responds to messages. You’ll learn the core concepts and have a working bot by the end.

Basic example

Here’s a minimal bot that responds to “ping” messages:
src/main.rs
use std::sync::Arc;
use whatsapp_rust::bot::Bot;
use whatsapp_rust::store::SqliteStore;
use whatsapp_rust_tokio_transport::TokioWebSocketTransportFactory;
use whatsapp_rust_ureq_http_client::UreqHttpClient;
use wacore::types::events::Event;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize storage backend
    let backend = Arc::new(SqliteStore::new("whatsapp.db").await?);

    // Build the bot
    let mut bot = Bot::builder()
        .with_backend(backend)
        .with_transport_factory(TokioWebSocketTransportFactory::new())
        .with_http_client(UreqHttpClient::new())
        .on_event(|event, client| async move {
            match event {
                Event::PairingQrCode { code, .. } => {
                    println!("Scan this QR code with WhatsApp:\n{}", code);
                }
                Event::Message(msg, info) => {
                    println!("Message from {}: {:?}", info.source.sender, msg);
                }
                _ => {}
            }
        })
        .build()
        .await?;

    // Start the bot
    bot.run().await?.await?;
    Ok(())
}

Step-by-step breakdown

1

Set up the storage backend

The bot needs persistent storage for session data, keys, and state:
let backend = Arc::new(SqliteStore::new("whatsapp.db").await?);
This creates a SQLite database file named whatsapp.db in your current directory. The session will persist across restarts.
2

Configure the bot builder

The Bot::builder() pattern lets you configure all required components:
let mut bot = Bot::builder()
    .with_backend(backend)
    .with_transport_factory(TokioWebSocketTransportFactory::new())
    .with_http_client(UreqHttpClient::new())
All three components (backend, transport, HTTP client) are required. The builder will return an error if any are missing.
3

Handle events

Use .on_event() to handle incoming events from WhatsApp:
.on_event(|event, client| async move {
    match event {
        Event::PairingQrCode { code, .. } => {
            println!("QR Code:\n{}", code);
        }
        Event::Message(msg, info) => {
            // Handle incoming message
        }
        Event::Connected(_) => {
            println!("Connected successfully!");
        }
        _ => {}
    }
})
The event handler receives two parameters:
  • event: The event type (QR code, message, connection status, etc.)
  • client: An Arc<Client> you can use to send messages or call API methods
4

Build and run the bot

Build the bot and start the event loop:
.build()
.await?;

bot.run().await?.await?;
The double .await? is intentional:
  • First .await? starts the bot and returns a JoinHandle
  • Second .await? waits for the bot to finish running

Responding to messages

Let’s extend the bot to respond to “ping” with “pong”:
use wacore::proto_helpers::MessageExt;
use waproto::whatsapp as wa;

.on_event(|event, client| async move {
    match event {
        Event::PairingQrCode { code, .. } => {
            println!("QR Code:\n{}", code);
        }
        Event::Message(msg, info) => {
            // Check if message is a text message saying "ping"
            if let Some(text) = msg.text_content() {
                if text == "ping" {
                    // Create reply message
                    let reply = wa::Message {
                        conversation: Some("pong".to_string()),
                        ..Default::default()
                    };

                    // Send the reply
                    if let Err(e) = client.send_message(info.source.chat, reply).await {
                        eprintln!("Failed to send reply: {}", e);
                    }
                }
            }
        }
        _ => {}
    }
})

Key methods

  • msg.text_content() - Extract text from any message type (conversation, extended text, etc.)
  • client.send_message() - Send a message to a chat
  • info.source.chat - The JID (identifier) of the chat where the message came from
  • info.source.sender - The JID of the user who sent the message

Authentication methods

QR code pairing (default)

The bot automatically generates QR codes when not authenticated. Scan with your phone to link:
Event::PairingQrCode { code, .. } => {
    println!("Scan this QR code:\n{}", code);
}

Pair code (phone number)

Alternatively, link using a phone number and 8-digit code:
use whatsapp_rust::pair_code::{PairCodeOptions, PlatformId};

let mut bot = Bot::builder()
    .with_backend(backend)
    .with_transport_factory(TokioWebSocketTransportFactory::new())
    .with_http_client(UreqHttpClient::new())
    .with_pair_code(PairCodeOptions {
        phone_number: "15551234567".to_string(),
        show_push_notification: true,
        custom_code: None,
        platform_id: PlatformId::Chrome,
        platform_display: "Chrome (Linux)".to_string(),
    })
    .on_event(|event, client| async move {
        match event {
            Event::PairingCode { code, .. } => {
                println!("Enter this code on your phone: {}", code);
            }
            _ => {}
        }
    })
    .build()
    .await?;
Pair code and QR code authentication run concurrently. Whichever method completes first will be used.

Running the bot

1

First run - Authentication

On the first run, the bot will generate a QR code:
cargo run
Scan the QR code with WhatsApp on your phone:
  1. Open WhatsApp on your phone
  2. Go to Settings → Linked Devices
  3. Tap “Link a Device”
  4. Scan the QR code displayed in your terminal
2

Subsequent runs - Auto-login

After pairing, the session is saved. The bot will automatically reconnect:
cargo run
You should see:
Connected successfully!
3

Test the bot

Send “ping” to your bot from any WhatsApp chat. It should reply with “pong”!

Complete example with logging

Here’s a production-ready example with proper logging:
src/main.rs
use chrono::Local;
use log::{error, info};
use std::sync::Arc;
use wacore::proto_helpers::MessageExt;
use wacore::types::events::Event;
use waproto::whatsapp as wa;
use whatsapp_rust::bot::Bot;
use whatsapp_rust::store::SqliteStore;
use whatsapp_rust_tokio_transport::TokioWebSocketTransportFactory;
use whatsapp_rust_ureq_http_client::UreqHttpClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Set up logging
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
        .format(|buf, record| {
            use std::io::Write;
            writeln!(
                buf,
                "{} [{}] - {}",
                Local::now().format("%H:%M:%S"),
                record.level(),
                record.args()
            )
        })
        .init();

    // Initialize backend
    let backend = Arc::new(SqliteStore::new("whatsapp.db").await?);
    info!("SQLite backend initialized");

    // Build bot
    let mut bot = Bot::builder()
        .with_backend(backend)
        .with_transport_factory(TokioWebSocketTransportFactory::new())
        .with_http_client(UreqHttpClient::new())
        .on_event(|event, client| async move {
            match event {
                Event::PairingQrCode { code, .. } => {
                    println!("\n{}", code);
                }
                Event::Message(msg, info) => {
                    if let Some(text) = msg.text_content() {
                        info!("Received: {} from {}", text, info.source.sender);

                        if text == "ping" {
                            let reply = wa::Message {
                                conversation: Some("pong".to_string()),
                                ..Default::default()
                            };

                            if let Err(e) = client.send_message(info.source.chat, reply).await {
                                error!("Failed to send reply: {}", e);
                            }
                        }
                    }
                }
                Event::Connected(_) => {
                    info!("✅ Bot connected successfully!");
                }
                Event::LoggedOut(_) => {
                    error!("❌ Bot was logged out");
                }
                _ => {}
            }
        })
        .build()
        .await?;

    info!("Starting bot...");
    bot.run().await?.await?;
    Ok(())
}

Next steps