Appearance
Your first bot
An echo bot: it receives a message and sends the same text back. About fifteen minutes, and everything larger is a variation on it.
You cannot run this yet
Step 1 is blocked. @gcore does not exist, no token can be issued, and the gateway returns 404 for every call — see implementation status.
The rest is written as a real walkthrough rather than a sketch, so the flow can be reviewed now and followed unchanged once the platform is up. Nothing below has been executed against a live server. Treat the code as reference, not as something you can paste and watch work.
1. Get a token
Bots are created by chatting with @gcore inside GROM. Send /newbot, answer two questions — a display name and a username ending in bot — and it replies with a token:
4210:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsawPut it in an environment variable now, before it ends up in a file you later push:
bash
export GROM_TOKEN='4210:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw'Check it:
bash
curl "https://api.casinoplaneta.info/bot$GROM_TOKEN/getMe"json
{
"ok": true,
"result": { "id": 4210, "is_bot": true, "first_name": "Echo", "username": "my_echo_bot" }
}getMe touches no chat and takes no parameters, which makes it the right thing to call when you want to know whether a token is good.
2. Say something to your bot
Open GROM, find @my_echo_bot, send it hello.
Nothing visible happens — that is correct. Your bot has no program behind it yet. The message is queued server-side, waiting to be collected.
This step is not optional, and it is not just for testing. A bot cannot open a conversation. Until a person messages it or adds it to a group, the bot has no way to reach them and no id to reach them with. Every "how do I message a user by phone number" question ends here: you cannot, by design.
3. Collect the update
bash
curl "https://api.casinoplaneta.info/bot$GROM_TOKEN/getUpdates"json
{
"ok": true,
"result": [
{
"update_id": 100001,
"message": {
"message_id": 7,
"from": { "id": 1337, "is_bot": false, "first_name": "Aziz" },
"chat": { "id": 1337, "type": "private" },
"date": 1770000000,
"text": "hello"
}
}
]
}Two ids matter and they are different things. update_id is your bookmark in the event stream. chat.id is where to reply — here it equals the sender's id because this is a private chat, but in a group it does not, and confusing the two sends your reply into the wrong conversation.
4. Reply
bash
curl "https://api.casinoplaneta.info/bot$GROM_TOKEN/sendMessage" \
-H 'Content-Type: application/json' \
-d '{"chat_id": 1337, "text": "hello"}'That is the whole API in one call: read chat.id, send text back.
5. Make it a loop
The polling loop is the part worth getting right, because one detail decides whether it works at all.
python
import requests
TOKEN = "..."
API = f"https://api.casinoplaneta.info/bot{TOKEN}"
offset = None
while True:
r = requests.get(f"{API}/getUpdates",
params={"offset": offset, "timeout": 50},
timeout=60).json()
if not r["ok"]:
print("error:", r["description"])
continue
for update in r["result"]:
# Acknowledge BEFORE handling: +1, always.
offset = update["update_id"] + 1
msg = update.get("message")
if not msg or "text" not in msg:
continue
requests.post(f"{API}/sendMessage",
json={"chat_id": msg["chat"]["id"], "text": msg["text"]})javascript
const TOKEN = '...'
const API = `https://api.casinoplaneta.info/bot${TOKEN}`
let offset
const call = async (method, params) =>
(await fetch(`${API}/${method}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
})).json()
while (true) {
const r = await call('getUpdates', { offset, timeout: 50 })
if (!r.ok) { console.error(r.description); continue }
for (const update of r.result) {
offset = update.update_id + 1 // +1, always
const msg = update.message
if (!msg?.text) continue
await call('sendMessage', { chat_id: msg.chat.id, text: msg.text })
}
}offset must be the last update_id plus one. Passing the id itself re-delivers that update forever, and the bot answers the same message on every pass — the single most common bug when writing a poller by hand.
timeout: 50 makes the request block until something arrives, so an idle bot costs one open connection instead of a hot loop. Set your HTTP client's own timeout higher than that, or your client will hang up on a perfectly healthy wait.
Run exactly one poller per token. A second one gets 409 Conflict, which usually means a deploy left the old process running.
6. When to switch to webhooks
Polling is fine — for development, and for plenty of production bots. Move to a webhook when you want the platform to push events to you instead:
bash
curl "https://api.casinoplaneta.info/bot$GROM_TOKEN/setWebhook" \
-H 'Content-Type: application/json' \
-d '{"url": "https://example.com/hook", "secret_token": "a-long-random-string"}'Three things to know before you do:
- Check the secret. Compare
X-Grom-Bot-Api-Secret-Tokenon every request. Without that check, anyone who guesses your URL can feed your bot fake updates. - Delivery is at-least-once. You will occasionally see the same
update_idtwice. Make the handler idempotent — store handled ids, or make the effect naturally repeatable. - Answer fast. Retries key off your response time, so a slow handler turns one update into several deliveries. Acknowledge with 200, then do the work.
Setting a webhook disables polling. To go back, call deleteWebhook.
Where to go from here
| Buttons and keyboards | Inline buttons and callback_query |
| Commands | /start, menus, per-language command lists |
| Errors | Which failures to retry and which are permanent |
| Limits | How fast you may send |
| Mini Apps | When a chat interface is not enough |