> ## Documentation Index
> Fetch the complete documentation index at: https://personal-9eca1d6c-claude-eager-cerf-0xyrbp.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Build your first WhatsApp bot in minutes

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:

```rust src/main.rs theme={null}
use std::sync::Arc;
use whatsapp_rust::bot::Bot;
use whatsapp_rust::TokioRuntime;
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())
        .with_runtime(TokioRuntime)
        .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

<Steps>
  <Step title="Set up the storage backend">
    The bot needs persistent storage for session data, keys, and state:

    ```rust theme={null}
    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.
  </Step>

  <Step title="Configure the bot builder">
    The `Bot::builder()` pattern lets you configure all required components:

    ```rust theme={null}
    let mut bot = Bot::builder()
        .with_backend(backend)
        .with_transport_factory(TokioWebSocketTransportFactory::new())
        .with_http_client(UreqHttpClient::new())
        .with_runtime(TokioRuntime)
    ```

    <Note>
      All four components (backend, transport, HTTP client, runtime) are required. The builder uses a typestate pattern — your code won't compile if any are missing.
    </Note>
  </Step>

  <Step title="Handle events">
    Use `.on_event()` to handle incoming events from WhatsApp:

    ```rust theme={null}
    .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`: An `Arc<Event>` — use `&*event` or `event.as_ref()` to pattern-match on the inner event type
    * `client`: An `Arc<Client>` you can use to send messages or call API methods
  </Step>

  <Step title="Build and run the bot">
    Build the bot and start the event loop:

    ```rust theme={null}
    .build()
    .await?;

    bot.run().await?.await?;
    ```

    The double `.await?` is intentional:

    * First `.await?` starts the bot and returns a `BotHandle`
    * Second `.await?` waits for the bot to finish running
  </Step>
</Steps>

## Responding to messages

Let's extend the bot to respond to "ping" with "pong":

```rust theme={null}
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.clone(), 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:

```rust theme={null}
Event::PairingQrCode { code, .. } => {
    println!("Scan this QR code:\n{}", code);
}
```

### Pair code (phone number)

Alternatively, link using a phone number and 8-digit code:

```rust theme={null}
use whatsapp_rust::pair_code::PairCodeOptions;

let mut bot = Bot::builder()
    .with_backend(backend)
    .with_transport_factory(TokioWebSocketTransportFactory::new())
    .with_http_client(UreqHttpClient::new())
    .with_runtime(TokioRuntime)
    .with_pair_code(PairCodeOptions {
        phone_number: "15551234567".to_string(),
        ..Default::default()
    })
    .on_event(|event, client| async move {
        match &*event {
            Event::PairingCode { code, .. } => {
                println!("Enter this code on your phone: {}", code);
            }
            _ => {}
        }
    })
    .build()
    .await?;
```

`PairCodeOptions` derives `companion_platform_id` and `companion_platform_display` from the device's `PlatformType` by default (Chrome with `Chrome (Linux)` for the stock web profile). You can override the wire id when needed:

```rust theme={null}
use whatsapp_rust::pair_code::PairCodeOptions;
use wacore::companion_reg::CompanionWebClientType;

PairCodeOptions {
    phone_number: "15551234567".to_string(),
    show_push_notification: true,
    custom_code: Some("ABCD1234".to_string()), // or None for random
    // `None` auto-derives from `Device.device_props.platform_type`.
    platform_id: Some(CompanionWebClientType::Chrome),
}
```

<Note>
  `platform_id` accepts the [`CompanionWebClientType`](/concepts/authentication#companionwebclienttype) wire enum (single-byte ASCII ids). The display string is always derived — there is no separate `platform_display` field.
</Note>

<Note>
  Pair code and QR code authentication run concurrently. Whichever method completes first will be used.
</Note>

## Running the bot

<Steps>
  <Step title="First run - Authentication">
    On the first run, the bot will generate a QR code:

    ```bash theme={null}
    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
  </Step>

  <Step title="Subsequent runs - Auto-login">
    After pairing, the session is saved. The bot will automatically reconnect:

    ```bash theme={null}
    cargo run
    ```

    You should see:

    ```
    Connected successfully!
    ```
  </Step>

  <Step title="Test the bot">
    Send "ping" to your bot from any WhatsApp chat. It should reply with "pong"!
  </Step>
</Steps>

### Demo binary CLI flags

The repository includes a demo bot binary (`src/main.rs`) that supports CLI arguments for authentication:

```bash theme={null}
cargo run                                      # QR code pairing only
cargo run -- --phone 15551234567               # Pair code + QR code (concurrent)
cargo run -- -p 15551234567                    # Short form
cargo run -- -p 15551234567 --code MYCODE12    # Custom 8-char pair code
cargo run -- -p 15551234567 -c MYCODE12        # Short form
```

The demo bot responds to `🦀ping` with a quoted `🏓 Pong!` reply, edits the reply to append the send latency, and supports media ping/pong via CDN reuse.

## Using MessageContext

For cleaner message handling, use `MessageContext` to wrap the message, metadata, and client together. This provides convenience methods like `send_message` (auto-targets the source chat), `build_quote_context`, `edit_message`, and `revoke_message`:

```rust theme={null}
use whatsapp_rust::bot::{Bot, MessageContext};

.on_event(|event, client| async move {
    if let Some(ctx) = MessageContext::from_event(&event, client) {
        handle_message(&ctx).await;
    }
})
```

Then define focused handler functions:

```rust theme={null}
async fn handle_message(ctx: &MessageContext) {
    if let Some(text) = ctx.message.text_content() {
        if text == "ping" {
            let reply = wa::Message {
                conversation: Some("pong".to_string()),
                ..Default::default()
            };
            if let Err(e) = ctx.send_message(reply).await {
                eprintln!("Failed to send: {}", e);
            }
        }
    }
}
```

### Media forwarding with CDN reuse

You can also forward media instantly by reusing the original CDN fields — no download or re-upload needed:

```rust theme={null}
/// Reuses the original CDN blob, only swaps the caption.
/// Instant regardless of file size.
fn build_media_reply(message: &wa::Message) -> Option<wa::Message> {
    let base = message.get_base_message();
    if let Some(img) = &base.image_message {
        return Some(wa::Message {
            image_message: Some(Box::new(wa::message::ImageMessage {
                caption: Some("Received your image!".to_string()),
                ..*img.clone()
            })),
            ..Default::default()
        });
    }
    None
}
```

See the [media forwarding guide](/guides/media-handling#forwarding-media-via-cdn-reuse) for more details.

## Complete example with logging

Here's a production-ready example with proper logging, reactions, message editing, and media CDN reuse:

```rust src/main.rs theme={null}
use chrono::{Local, Utc};
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, MessageContext};
use whatsapp_rust::TokioRuntime;
use whatsapp_rust::store::SqliteStore;
use whatsapp_rust_tokio_transport::TokioWebSocketTransportFactory;
use whatsapp_rust_ureq_http_client::UreqHttpClient;

const PING_TRIGGER: &str = "🦀ping";
const PONG_TEXT: &str = "🏓 Pong!";

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

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

    let mut bot = Bot::builder()
        .with_backend(backend)
        .with_transport_factory(TokioWebSocketTransportFactory::new())
        .with_http_client(UreqHttpClient::new())
        .with_runtime(TokioRuntime)
        .on_event(|event, client| async move {
            match &*event {
                Event::PairingQrCode { code, .. } => {
                    println!("\n{}", code);
                }
                Event::Message(msg, info) => {
                    let ctx = MessageContext::from_parts(msg, info, client);
                    handle_message(&ctx).await;
                }
                Event::Connected(_) => info!("Bot connected!"),
                Event::LoggedOut(_) => error!("Bot was logged out!"),
                _ => {}
            }
        })
        .build()
        .await?;

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

async fn handle_message(ctx: &MessageContext) {
    // Try CDN-reuse media reply first (instant, no download needed)
    if let Some(reply) = build_media_pong(&ctx.message) {
        if let Err(e) = ctx.send_message(reply).await {
            error!("Failed to send media pong: {}", e);
        }
        return;
    }

    // Handle text ping
    if ctx.message.text_content() == Some(PING_TRIGGER) {
        let context_info = ctx.build_quote_context();
        let reply = wa::Message {
            extended_text_message: Some(Box::new(wa::message::ExtendedTextMessage {
                text: Some(PONG_TEXT.to_string()),
                context_info: Some(Box::new(context_info)),
                ..Default::default()
            })),
            ..Default::default()
        };

        if let Err(e) = ctx.send_message(reply).await {
            error!("Failed to send pong: {}", e);
        }
    }
}

/// Reuses the original CDN blob, only swaps the caption.
/// Instant regardless of file size — no download or re-upload.
fn build_media_pong(message: &wa::Message) -> Option<wa::Message> {
    let base = message.get_base_message();
    if let Some(img) = &base.image_message
        && img.caption.as_deref() == Some(PING_TRIGGER)
    {
        return Some(wa::Message {
            image_message: Some(Box::new(wa::message::ImageMessage {
                caption: Some(PONG_TEXT.to_string()),
                ..*img.clone()
            })),
            ..Default::default()
        });
    }
    if let Some(vid) = &base.video_message
        && vid.caption.as_deref() == Some(PING_TRIGGER)
    {
        return Some(wa::Message {
            video_message: Some(Box::new(wa::message::VideoMessage {
                caption: Some(PONG_TEXT.to_string()),
                ..*vid.clone()
            })),
            ..Default::default()
        });
    }
    None
}
```

## Configuring log targets

whatsapp-rust uses the `log` crate with module-specific targets for fine-grained filtering. You can use `RUST_LOG` to control which components emit log output.

### Available log targets

| Target                  | Description                                                                                               |
| ----------------------- | --------------------------------------------------------------------------------------------------------- |
| `Client/Keepalive`      | Keepalive ping/pong and dead socket detection                                                             |
| `Client/Recv`           | Incoming frame processing (unmarshal, decompress)                                                         |
| `Client/Send`           | Outgoing message encryption and dispatch                                                                  |
| `Client/OfflineSync`    | Offline message sync progress and timing                                                                  |
| `Client/AppState`       | App state sync (contacts, settings, etc.)                                                                 |
| `Client/AccountSync`    | Account-level sync operations                                                                             |
| `Client/PairCode`       | Pair code authentication flow                                                                             |
| `Client/Pair`           | QR code pairing flow                                                                                      |
| `Client/Receipt`        | Receipt processing (read, delivered, played)                                                              |
| `Client/TcToken`        | Trusted contact token operations                                                                          |
| `Client/Group`          | Group metadata and participant operations                                                                 |
| `Client/Contacts`       | Contact sync and lookup                                                                                   |
| `Client/Business`       | Business profile updates                                                                                  |
| `Client/PDO`            | Peer Data Operations — message recovery from primary phone via bare-JID peer messaging and retry fallback |
| `Client/IQ`             | IQ stanza send/receive                                                                                    |
| `Client/Ack`            | Stanza acknowledgment                                                                                     |
| `Client/Status`         | Status/story operations                                                                                   |
| `Client/Picture`        | Profile picture updates                                                                                   |
| `Client/UnifiedSession` | Session establishment                                                                                     |
| `Blocking`              | Block/unblock operations                                                                                  |
| `Chatstate`             | Typing indicator events                                                                                   |
| `PresenceHandler`       | Presence update processing                                                                                |
| `AppState`              | App state patch encoding/decoding                                                                         |
| `Bot/PairCode`          | Bot-level pair code handling                                                                              |

### Filtering examples

```bash theme={null}
# Show only connection and messaging logs
RUST_LOG="Client/Keepalive=debug,Client/Send=debug,Client/Recv=trace" cargo run

# Debug offline sync issues
RUST_LOG="Client/OfflineSync=debug" cargo run

# Quiet mode: only errors and warnings
RUST_LOG="warn" cargo run

# Verbose: all client internals at debug level
RUST_LOG="debug" cargo run
```

<Note>
  During shutdown or disconnect, the client automatically downgrades sync errors from `error` to `debug` level to reduce noise. This means you won't see spurious error logs when the client is intentionally disconnecting.
</Note>

## Running with Docker

You can also run the bot using Docker instead of compiling locally:

```bash theme={null}
docker build -t whatsapp-rust .
docker run -v ./data:/data whatsapp-rust
```

Session data is stored in the `/data` directory inside the container. Mount a volume to persist it across restarts. The container shuts down gracefully on `docker stop` — the bot disconnects cleanly from WhatsApp before exiting. See the [installation guide](/installation#docker-deployment) for more details.

## Benchmarking

The repository includes a benchmark example at `examples/benchmark.rs` that you can use for quick integration-level performance testing. It uses an in-memory backend and supports a custom WebSocket URL via the `WHATSAPP_WS_URL` environment variable:

```bash theme={null}
cargo run --example benchmark --features danger-skip-tls-verify
```

<Note>
  The benchmark example requires the `danger-skip-tls-verify` feature flag because it's designed for use with local test servers.
</Note>

For more comprehensive integration benchmarks with allocation tracking, the `bench-integration` test suite measures real-world scenarios (connect, send, receive, reconnect) and reports wall-clock time plus heap allocation counts per operation:

```bash theme={null}
# Requires a mock server (e.g., Bartender)
MOCK_SERVER_URL="wss://127.0.0.1:8080/ws/chat" \
  cargo run -p bench-integration --release
```

For low-level protocol benchmarks, the `wacore` crate includes an [iai-callgrind](https://github.com/iai-callgrind/iai-callgrind) benchmark suite that measures instruction counts for the full send/receive pipeline (DM and group messaging with various participant counts), binary protocol encoding, Signal Protocol operations, and reporting token generation:

```bash theme={null}
# Run all wacore benchmarks (requires valgrind and iai-callgrind-runner)
cargo bench --workspace
```

See the [wacore benchmarks documentation](/api/wacore#benchmarks) for details on each suite, allocation optimizations, and CI integration.

## Next steps

<CardGroup cols={2}>
  <Card title="Sending messages" icon="message" href="/guides/sending-messages">
    Learn about different message types and how to send them
  </Card>

  <Card title="Media handling" icon="image" href="/guides/media-handling">
    Upload and download images, videos, and documents
  </Card>

  <Card title="Group management" icon="users" href="/guides/group-management">
    Create and manage WhatsApp groups
  </Card>

  <Card title="Client API reference" icon="code" href="/api/client">
    Explore all available client methods
  </Card>
</CardGroup>
