← Back to Qwing Bot platform

Build bots on Qwing

Mint a bot identity inside the app, run the self-hostable SDK on your own server, and answer messages. Every bot holds its own post-quantum keys and runs the Double Ratchet locally — Qwing only ever relays ciphertext it cannot read.

STEP 01

Mint a bot in the app

Open Qwing → Settings → My Bots → Create. Set a name, username, description and emoji avatar. You receive a 24-word recovery phrase, shown once — that phrase is the bot's token.

STEP 02

Run the SDK on your server

Plug that token into the Rust SDK, define how the bot replies, and run it. It publishes prekeys, holds the single-instance lock, and polls forever — no infrastructure of ours to depend on.

Overview

What a Qwing bot is

A bot is a normal Qwing account you control from code. It has its own ML-DSA-87 identity, its own ML-KEM-1024 ratchet, and it talks to users over the exact same post-quantum end-to-end-encrypted channel that people use. There is no bot API that sees plaintext: the server stores and forwards ciphertext, and your bot process does the decryption locally with keys derived from its token.

Language Rust 1.77+ Runtime tokio async Crypto ML-KEM-1024 · ML-DSA-87 Hosting self-hosted

Create

Mint the bot identity

Bots are created from inside Qwing, not on a website — the private key never leaves your device until you copy it. In Settings → My Bots, accept the Bot Developer Agreement, then set the profile (name, username, description, single-emoji avatar). Qwing shows the bot's 24-word recovery phrase exactly once. Store it somewhere safe.

The phrase is the token. Anyone who has it controls the bot, and it is never sent to any server. Lose it and the bot is gone — mint a new one. Revoking a bot from the app is irreversible: the account is permanently disabled.

Quickstart

Run the SDK

The SDK is a small self-hostable Rust crate. Give it the token, register your handlers, and call run() — it connects, publishes prekeys, and polls forever.

use qwing_bot_sdk::BotClient;

#[tokio::main]
async fn main() -> qwing_bot_sdk::Result<()> {
    let mut bot = BotClient::from_token(&std::env::var("BOT_TOKEN")?)
        .connect().await?;          // derives identity, publishes prekeys

    bot.on_message(|msg, ctx| async move {
        match msg.text.as_str() {
            "/start" => ctx.reply("Hi! Send me anything and I'll echo it.").await,
            other  => ctx.reply(&format!("echo: {other}")).await,
        }
    });

    bot.run().await   // polls forever
}

Set the token (and, if needed, the API endpoint) in the environment:

# liboqs must be on the library path at build and run time
export BOT_TOKEN="word1 word2 … word24"
export BOT_BACKEND="https://api.quantumwing.io/api/v1"
cargo run --release
Optional BOT_PROXY — route over Tor / SOCKS5 Data dir ./qwing-echo-data — session state at rest

Handlers

Commands & replies

There is no fixed command set — you decide what the bot understands inside on_message by matching on msg.text. Commands are just messages that start with / by convention. The context object gives you the reply primitives:

  • ctx.reply(text)Send a plain text reply to the user who messaged the bot.
  • ctx.reply_with_buttons(text, rows)Reply with tappable inline buttons; each carries a callback payload.
  • bot.on_button(cb)Handle a button tap — cb.data is the payload you attached.
  • msg.textThe decrypted inbound text. Match it to route /start, /help, or anything you define.
bot.on_message(|msg, ctx| async move {
    match msg.text.as_str() {
        "/start"   => ctx.reply("Welcome. Try /menu").await,
        "/menu"    => ctx.reply_with_buttons("Pick one:",
                        &[&[("Weather", "pick:weather"), ("News", "pick:news")]]).await,
        _          => ctx.reply("Unknown command — try /start").await,
    }
});
bot.on_button(|cb, ctx| async move {
    ctx.reply(&format!("You picked {}", cb.data)).await
});

Rules

One token, one instance

Run exactly one process per token. The bot holds a single-instance lock; two processes on the same token would fork the ratchet and permanently break decryption for the people it talks to. If you redeploy, stop the old instance first, and keep the bot's data directory with it — that is where its session state lives.

Privacy

What the server sees

The same as for any Qwing chat: nothing readable. Messages between a user and your bot are end-to-end encrypted with post-quantum cryptography; the server relays ciphertext and cannot read it. Your bot is the other end of that encryption, so it naturally sees the messages sent to it — and nothing else. It has no window into any other conversation.

Ready to build? Mint a bot in the app and point the SDK at its token.