Integration
Vote rewards
Users can vote for your bot once every 24 hours. Give them something back, like credits, a premium day or a badge, by checking their vote from your bot.
Pick a method
| Method | Use it when |
|---|---|
| Polling | Your bot asks us when a user claims a reward, e.g. a /claim button. Simplest; no public server needed. |
| Webhook | We POST to your server the moment someone votes, so you can reward them without them asking. |
Both live under Dashboard → your bot → Integration. You can use either or both. Users are identified by their Telegram user ID, the same from.id your bot sees in updates.
Send users to your listing to vote: https://tglist.ing/bots/your-bot-username.
Polling: the vote-check API
Get an API key
On the Integration page, press Create key. The key is shown once; store it as an environment variable on your bot server (the examples use TGLIST_API_KEY). Rotating creates a new key and the old one stops working immediately.
Request
GET https://tglist.ing/api/integrations/vote-status?user_id=123456789
Authorization: Bearer YOUR_API_KEYResponse
{
"voted": true,
"lastVotedAt": "2026-08-02T10:30:00.000Z",
"nextVoteAt": "2026-08-03T10:30:00.000Z"
}votedistrueif the user voted in the last 24 hours.lastVotedAtandnextVoteAtarenullif they have never voted. UsenextVoteAtto tell users when they can vote again.- Errors:
401for a missing or revoked key,400withoutuser_id,429above 120 requests a minute per bot.
voted stays true for 24 hours, so a user can claim many times inside one vote. Store lastVotedAt with each reward you grant and only reward again when it changes.Examples
const res = await fetch(`/api/integrations/vote-status?user_id=${telegramUserId}`, {
headers: { Authorization: `Bearer ${process.env.TGLIST_API_KEY}` },
});
const { voted } = await res.json();
if (voted) await grantReward(telegramUserId);Webhook: get told when someone votes
Set it up
- On the Integration page, switch to Webhook and enter a public
https://URL on your server. - Copy the signing secret (
whsec_…) into your server's environment, e.g.TGLIST_WEBHOOK_SECRET. - Press Test endpoint. It sends a
pingevent; reply with any 2xx.
Payload
Each vote is a JSON POST:
{
"id": "cmh2x9v0k0001",
"type": "vote.created",
"createdAt": "2026-08-02T10:30:00.000Z",
"data": {
"botId": "cmgq1b2c30000",
"user": { "id": 123456789, "username": "alice" },
"votedAt": "2026-08-02T10:30:00.000Z",
"nextVoteAt": "2026-08-03T10:30:00.000Z"
}
}data.user.id is the voter's Telegram user ID. username can be null.
Headers
Content-Type: application/json
X-Tglist-Event: vote.created
X-Tglist-Event-Id: cmh2x9v0k0001
X-Tglist-Signature: t=1754130600,v1=5f2b9c…Verify the signature
v1 is the hex HMAC-SHA256 of {t}.{raw body}, keyed with your signing secret. Compute it over the raw request bytes, not re-serialized JSON. Reject with 401 if it doesn't match or t is more than 5 minutes old.
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.TGLIST_WEBHOOK_SECRET;
// Use the raw body: the signature covers the exact bytes we sent.
app.post("/tglist/webhook", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("X-Tglist-Signature") ?? "";
const { t, v1 = "" } = Object.fromEntries(header.split(",").map((part) => part.split("=")));
const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${req.body}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
const valid = v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
if (!fresh || !valid) return res.sendStatus(401);
const event = JSON.parse(req.body);
if (event.type === "vote.created") grantReward(event.data.user.id);
res.sendStatus(200);
});Stuck on a mismatch? Paste a real delivery below to see what the signature should be.
Try it: check a signature
Fill in all three fields to check.
Runs in your browser. Nothing you type here is sent anywhere.
Delivery rules
- Reply with a 2xx within 5 seconds. Redirects aren't followed.
- Failures are retried after 30 s, 2 min, 10 min, 1 h, 6 h, 12 h and 24 h, then dropped (about 43 hours after the vote).
- The same event can arrive more than once. Skip any
X-Tglist-Event-Idyou've already handled. - After 5 failures in a row we message you on Telegram. After 50 the webhook is turned off; fix your endpoint and press Enable on the Integration page.
- The URL must be a public domain over
https://. IP addresses and private hosts are refused.
Checklist
- Keys and secrets live in environment variables, never in your bot's code or chats.
- You reward once per vote, not once per check.
- Your bot links to the listing, e.g. from a
/votecommand. - Webhook failure alerts are on in Notifications.