XTREMETOP300

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": [] }
Por que duas chamadas? Se o seu servidor cair entre a consulta e a entrega, a recompensa continua lá no próximo ciclo. É o resgate que diz “o jogador já recebeu”. Se preferir fazer uma chamada só, envie "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 more e 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.