API de recompensa por voto
Dê aos seus jogadores uma recompensa no jogo por votar. Duas formas de receber os votos, as duas usando uma única chave, e as duas pensadas para funcionar em um servidor atrás de NAT ou que reinicia duas vezes por dia.
Qual das duas eu uso?
Pull — comece por aqui
O seu servidor pergunta para a gente, a cada um ou dois minutos, o que ele deve. Não precisa de porta aberta, domínio nem certificado TLS. Funciona até em conexão doméstica.
Push — se você tem um endpoint web
Fazemos um POST na sua URL no momento em que o voto chega, assinado para você poder provar que fomos nós. Instantâneo, mas só se a gente conseguir alcançar você.
Dá para usar os dois. Uma recompensa enviada para o seu callback continua disponível via pull, então um push perdido nunca vira recompensa perdida.
1. Pegue a sua chave
Abra o seu servidor em Meus servidores → Recompensas por voto, ative as recompensas e copie a chave da API. Envie a chave como header em toda chamada:
Authorization: Bearer YOUR_API_KEY
Nunca coloque a chave em uma URL. Ela acaba indo parar em logs de acesso, logs de proxy e no histórico do navegador.
2. Veja o que você deve
POST /api/v1/rewards/pending
retorna as recompensas ainda não resgatadas, das mais antigas para as mais novas.
curl -X POST https://xtremetop300.com/api/v1/rewards/pending \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"limit": 50}'
{
"server": { "id": 1234, "name": "My Server" },
"count": 2,
"more": false,
"rewards": [
{ "token": "8f2c...", "player": "Steve", "server_id": 1234, "voted_at": "2026-08-25T09:14:02+00:00" },
{ "token": "1a90...", "player": "Alexia", "server_id": 1234, "voted_at": "2026-08-25T09:31:44+00:00" }
]
}
Consultar não consome nada. Entregue as recompensas e depois confirme:
curl -X POST https://xtremetop300.com/api/v1/rewards/claim \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"tokens": ["8f2c...", "1a90..."]}'
{ "claimed": ["8f2c...", "1a90..."], "already_claimed": [], "unknown": [] }
"claim": true junto com a
requisição de pending — você recebe as recompensas e elas já são marcadas como entregues
na hora, mas uma queda no momento errado faz você perdê-las.
3. Ou só faça uma pergunta de sim ou não
A maioria dos plugins só quer saber se precisa cobrar o voto de alguém no login.
GET /api/v1/rewards/check
responde pelo nome do jogador ou pelo endereço IP.
curl "https://xtremetop300.com/api/v1/rewards/check?player=Steve&hours=24" \
-H "Authorization: Bearer YOUR_API_KEY"
{ "voted": true, "voted_at": "2026-08-25T09:14:02+00:00", "window_hours": 24 }
4. Push, se você quer na hora
Defina uma URL de callback nas configurações de recompensa e fazemos este POST nela a cada voto que chega:
POST /your-callback HTTP/1.1
Content-Type: application/json
X-Xtreme300-Signature: sha256=4f1e...
X-Xtreme300-Timestamp: 1787654321
X-Xtreme300-Delivery: 8f2c...
{
"event": "vote.reward",
"sent_at": 1787654321,
"reward": { "token": "8f2c...", "player": "Steve", "server_id": 1234, "voted_at": "..." }
}
Responda com qualquer status 2xx. Qualquer outra resposta é reenviada 5 vezes ao longo da hora seguinte e, depois disso, a recompensa simplesmente fica esperando o pull. Um 4xx é tratado como recusa e não é reenviado, então não retorne um 4xx para um problema temporário.
5. Verifique a assinatura
Qualquer um pode fazer POST na sua URL de callback. É a assinatura que separa um voto de
verdade de um desconhecido dando diamantes para si mesmo. Calcule
HMAC-SHA256(timestamp + "." + rawBody)
com a sua chave da API e compare com o header.
// PHP
$body = file_get_contents('php://input'); // the raw body, before json_decode
$timestamp = (int) $_SERVER['HTTP_X_XTREME300_TIMESTAMP'];
$signature = $_SERVER['HTTP_X_XTREME300_SIGNATURE'];
// Reject old requests first. A valid signature on a captured request replayed
// a thousand times is the whole attack.
if (abs(time() - $timestamp) > 300) {
http_response_code(400);
exit;
}
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $apiKey);
// hash_equals, not ==. A normal comparison returns faster the earlier it finds
// a difference, which is enough to guess the signature one byte at a time.
if (! hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
// Java (Bukkit / Paper / Spigot)
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] digest = mac.doFinal((timestamp + "." + rawBody).getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder("sha256=");
for (byte b : digest) hex.append(String.format("%02x", b));
if (!MessageDigest.isEqual(hex.toString().getBytes(), signatureHeader.getBytes())) {
return; // not from us
}
6. Mande para os jogadores um link que já sabe quem eles são
Adicione o nome do jogador ao link de votação e o campo já chega preenchido, então ele só
precisa clicar. Qualquer um destes funciona:
player,
pingUsername,
username ou
nick — os extras existem
para que um botão que você já fez para outro toplist possa ser apontado para cá sem mudar nada.
https://xtremetop300.com/vote/1234?player=Steve
Regras que vale a pena conhecer
- Uma recompensa só é criada para um voto que foi contabilizado. Votos sinalizados pelas nossas verificações antifraude não geram nada, então uma farm de votos não consegue colher a moeda do seu jogo sem afetar o seu ranking.
- Um voto por jogador a cada 24 horas, o mesmo limite do próprio toplist.
- Trate todo token como entregue pelo menos uma vez (at-least-once). Guarde os que você já pagou e ignore as repetições. Essa é a única coisa que um plugin obrigatoriamente precisa acertar.
- Nunca cole um nome de jogador direto em um comando de console. Limitamos os nomes a letras, números, espaços, pontos, hífens e underlines, mas o seu servidor ainda deve verificar se o nome pertence a uma conta real antes de pagar.
- 50 recompensas por pull por padrão,
200 no máximo. Fique de olho no campo
moree chame de novo se ele for true. - 60 requisições por minuto por servidor. Consultar uma vez por minuto usa uma delas.
- Recompensas não resgatadas são excluídas após 14 dias.
Erros
- 401
- Chave ausente ou errada. Confira o prefixo
Bearer. - 403
- A chave está certa, mas o servidor não está no ar no site ou as recompensas estão desativadas.
- 422
- Algum parâmetro está faltando ou mal formatado; o corpo da resposta diz qual.
- 429
- Requisições demais. Espere um pouco e tente de novo — nada se perde.
Já está listado? Abra as configurações do seu servidor para ativar as recompensas. Ainda não está listado? Adicione o seu servidor.