Getting started
Create a bot in the Qivuna app with @qivbot, reveal its token, and run an echo bot. About ten minutes.
Before you start#
To create bots, your Qivuna account needs:
- to be at least 7 days old;
- 2-step verification with an authenticator app or a 2-step password (Settings → Privacy and security). SMS codes do not count: they protect your bots' tokens and keys, so they must be something only you hold;
- to accept the Developer Terms (QivBot shows them);
- bots to be available on your account. They are open to every Qivuna account that meets the conditions above.
Each account may own 2 bots; 10 once it has been a bot developer for 30 days without any content strike.
1. Create the bot with @qivbot#
- Open @qivbot in the Qivuna app (search for
qivbot, or openhttps://qivuna.com/b/qivbot) and press Start. It answers in Hebrew if your app is in Hebrew, otherwise in English. - Tap New bot (or send
/newbot). If something from the list above is missing, QivBot says what, with a button to fix it. - Read the Developer Terms and tap I agree.
- Send the bot's name: 1–64 characters. Check marks, “official”, “verified” and the name Qivuna are not allowed in names of bots without the blue check.
- Send its username (handle): 7–32 characters, starting with a letter, letters, digits and
_, ending inbot— for exampleweather_bot. Names that look like Qivuna's own (including look-alike letters) are refused. - Choose Encrypted (recommended) or Not encrypted. An unencrypted bot is labelled “not end-to-end encrypted” for everyone and cannot work in encrypted groups. You can switch a bot to encrypted later, never back. (This step appears once encrypted bots are switched on for the platform; until then bots are created unencrypted.)
QivBot answers “@weather_bot is ready! It's in development mode” with buttons for the token, settings, commands and publishing.
2. Reveal the token#
Tap Reveal token. The app asks for your authenticator code or 2-step password and shows the token once, on a secure screen:
7123456789:qvb1_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789_-AbCdE
The part before the colon is the bot number; the rest is a 256-bit secret. Treat it like a password:
- Send it only in the header
Authorization: Bot <token>. A token that appears in a URL is revoked automatically and QivBot tells you. - Revealing a new token replaces the old one, which keeps working for 10 more minutes so you can deploy. At most 5 reveals per hour.
- If it leaks, revoke every token at once from QivBot (Token → Revoke all tokens, confirmed in the app).
Keep it in an environment variable, not in code:
export QIVUNA_BOT_TOKEN='7123456789:qvb1_…'
3. Run an echo bot#
Node.js (20.11 or later)#
npm install @qivuna/bot-sdk
echo-bot.mjs
import { Bot, FileStateStore } from "@qivuna/bot-sdk";
const bot = new Bot({
token: process.env.QIVUNA_BOT_TOKEN,
store: new FileStateStore("./bot-state"), // replay memory, keys, pins: keep and back up
});
bot.onMessage(async (ctx) => {
if (ctx.text) await ctx.reply(ctx.text, { quote: true });
});
bot.onCallbackQuery(async (ctx) => {
await ctx.answer(`You pressed ${ctx.data}`);
});
process.once("SIGINT", () => bot.stop());
process.once("SIGTERM", () => bot.stop());
await bot.startPolling();
await bot.store.close();
node echo-bot.mjs
Python (3.9 or later)#
pip install qivuna-bot-sdk
echo_bot.py
import os, signal
from qivuna_bot import Bot, FileStateStore
bot = Bot(os.environ["QIVUNA_BOT_TOKEN"], FileStateStore("./bot-state"))
@bot.on_message
def echo(ctx):
if ctx.text:
ctx.reply(ctx.text, quote=True)
@bot.on_callback_query
def pressed(ctx):
ctx.answer("You pressed %s" % ctx.data)
signal.signal(signal.SIGINT, lambda *a: bot.stop())
bot.run_polling()
bot.store.close()
python echo_bot.py
Without an SDK#
The API is plain JSON over HTTPS. A minimal loop: long-poll, answer, acknowledge.
OFFSET=0
while true; do
RESP=$(curl -s "https://bots.qivuna.com/v1/getUpdates" \
-H "Authorization: Bot $QIVUNA_BOT_TOKEN" -H "Content-Type: application/json" \
-d "{\"offset\": $OFFSET, \"timeout\": 25}")
if [ "$(echo "$RESP" | jq .ok)" != "true" ]; then echo "$RESP" >&2; sleep 5; continue; fi
echo "$RESP" | jq -c '.result[]' | while read -r U; do
CHAT=$(echo "$U" | jq '.message.chat.id // empty')
TEXT=$(echo "$U" | jq '.message.text // empty')
[ -n "$CHAT" ] && [ -n "$TEXT" ] && curl -s "https://bots.qivuna.com/v1/sendMessage" \
-H "Authorization: Bot $QIVUNA_BOT_TOKEN" -H "Content-Type: application/json" \
-d "{\"chat_id\": $CHAT, \"text\": $TEXT}" > /dev/null
done
LAST=$(echo "$RESP" | jq '.result[-1].update_id // empty')
[ -n "$LAST" ] && OFFSET=$((LAST + 1))
done
A real bot should also handle errors and retry_after; see Errors.
4. Try it#
- Open
https://qivuna.com/b/<your bot's username>on your phone (or find the bot from QivBot → My bots) and press Start. Your bot receives abot_startedupdate. - Send it a message; it echoes it back.
- Add it to a small group of your own: group info → Bots → Add → search its username → choose its rights. In development mode the group must be yours and have at most 10 members (see Development mode).
- In the group, send
/start@your_botor mention it: with privacy mode on, that is what it sees.
If you chose Encrypted#
Nobody can start an encrypted bot until it has an active key: the app says “This bot isn't ready yet”. The key pair is made on your server, and you, the owner, confirm it on your phone:
await bot.init();
let status = await bot.syncKeys();
if (!status.canSeal) {
await bot.generateKey(); // private key stays in ./bot-state
const { fingerprint } = await bot.proposeKey(); // setMyEncryptionKey
console.log("security code:", fingerprint);
while (!(status = await bot.syncKeys()).canSeal) await new Promise((r) => setTimeout(r, 10_000));
}
bot.onEncryptedMessage(async (req) => { await req.reply(`You said: ${req.text}`); });
await bot.startPolling();
Then in the app: QivBot → your bot → Encryption → Confirm new code. The confirmation screen computes the security code on your phone; check that it matches the one your server printed, tap It matches and confirm with your authenticator code or 2-step password. The full story, including what this protects, is in Encrypted bots.
Next steps#
- Give the bot a description and commands (QivBot → your bot, or
setMyDescriptionandsetMyCommands), then Publish it. - Run the poller under a process manager (systemd, pm2, a container restart policy) so it comes back after a crash. One poller per bot: a second one gets
409 conflict. Webhooks are not open yet. - Read Concepts before adding the bot to rooms you don't own.