SDKs and test vectors
Two official SDKs cover the whole Bot API, long polling and webhooks, and the bot side of end-to-end encryption. Both are checked byte for byte against the same test vectors.
Packages#
| Package | Runtime | Dependencies | Install |
|---|---|---|---|
@qivuna/bot-sdk | Node.js 20.11+ | none (Node's built-in crypto) | npm install @qivuna/bot-sdk |
qivuna-bot-sdk | Python 3.9+ | cryptography (pyca), tested with 46.0.5 | pip install qivuna-bot-sdk |
Both include runnable examples: a plain echo bot and an encrypted echo bot (polling or webhook).
What they do for you#
- API client with a method for every Bot API method (camelCase in Node, snake_case in Python) and a generic
call(method, params). Refusals raiseQivunaApiErrorwithstatus,code,message,retryAfter,parameterandrequestId. - Safe retries only: every write carries an
Idempotency-Key; 429 and other answers withretry_afterare waited for; lost connections and 5xx are retried for reads and keyed writes;platform_pausedis never retried; redirects are never followed. The token goes only in theAuthorizationheader. - Long polling that acknowledges an update only after your handler succeeded, with retries (1, 2, 4, 8 s), a dead letter after 5 attempts, and leases so a slow handler is not run twice.
- Webhook handlers: Node
http, Express and Fastify; Python WSGI and ASGI. In order: body size, timestamp within 300 s, HMAC v1 (any of your secrets), only thenqbw1opening, de-duplication on the signedupdate_id, then your handler. They answer the ownership challenge. - Encrypted bots: key generation,
pop, proposal, key sync with key alerts, opening and checking requests, pinning people's keys with compare-and-set, signed and sealed replies and answers, reply tokens,retecho, and the phones' content rules before signing. - A state store (
FileStateStore): an fsync'ed journal, a single-process lock, permission checks, optional encryption of keys at rest with a KEK. For several workers, implement theStateStoreinterface on your database (compare-and-set pins, leases, fail-closed loading).
Read the security section of each SDK's README before running an encrypted bot in production; the essentials are on Encrypted bots.
Quick start#
Node.js
import { Bot, FileStateStore } from "@qivuna/bot-sdk";
const bot = new Bot({ token: process.env.QIVUNA_BOT_TOKEN, store: new FileStateStore("./bot-state") });
bot.onMessage(async (ctx) => { if (ctx.text) await ctx.reply(ctx.text); });
await bot.startPolling();
Python
from qivuna_bot import Bot, FileStateStore
bot = Bot(TOKEN, FileStateStore("./bot-state"))
@bot.on_message
def echo(ctx):
if ctx.text:
ctx.reply(ctx.text)
bot.run_polling()
Webhook instead of polling (webhooks are not open yet; until then setWebhook answers 400 webhooks_disabled):
// Node.js + Express: the handler needs the RAW body
const handle = bot.webhookHandler({ secrets: [SECRET], encryptedPayload: false });
app.post("/qivuna", express.raw({ type: "*/*", limit: "1mb" }), async (req, res) => {
const r = await handle({ headers: req.headers, body: req.body });
res.status(r.status).set(r.headers).send(r.body);
});
# Python (WSGI: gunicorn, Flask's server …; or asgi_app for uvicorn)
from qivuna_bot import wsgi_app
app = wsgi_app(bot, [SECRET], encrypted_payload=False, path="/qivuna")
Set your endpoint's secrets before calling setWebhook, so that the ownership challenge verifies.
Test vectors#
Every byte format of the bot platform — pseudonymous ids, the token hash, webhook HMAC (single and dual), qbw1, pop, key certificates, user bot keys, return addresses, attachment records, QB1 requests and bot messages, s2 envelopes, fingerprints, strict Ed25519 and refused X25519 keys — has known-answer vectors, positives and negatives, generated by an independent reference implementation (Python, pyca cryptography 46.0.5).
- File:
bots_vectors.json - SHA-256:
cb393c0dde838720ef6a1e93cbc708b51c79b4b20d7b84201ee180f0f8d3e57d
sha256sum bots_vectors.json # Linux
shasum -a 256 bots_vectors.json # macOS
The vectors are normative. Where any prose, including these pages, disagrees with them, the vectors win. Both SDKs assert every bot-side vector and have a coverage test that fails if one is skipped; Qivuna's server and apps reproduce the same file. If you write your own implementation of the encrypted parts, reproduce every vector it touches, negatives included — in particular: refuse duplicate JSON keys, use strict cofactorless Ed25519, and refuse small-order X25519 keys.
The fixed random values in the vectors exist only to make them reproducible. Production code must draw all randomness from a CSPRNG and never accept it from callers.