Para desenvolvedores

Integração e API

Recompense dentro do seu jogo quem vota nele no TopIdle. Você recebe um evento assinado a cada voto confirmado, ou consulta quando quiser — as duas formas entregam o mesmo dado.

Base: https://topidle.com · Última atualização: 1º de setembro de 2026

Como começar

A integração fica disponível para jogos verificados. Todo o cadastro é feito no painel da sua equipe, dentro do site.

  1. Verifique o jogo. Cadastre o endereço oficial e comprove o controle do domínio por DNS, meta tag ou e-mail do domínio.
  2. Abra o painel da equipe e vá em Integração. Lá ficam a chave da API, o segredo do webhook e o endereço que vamos chamar.
  3. Escolha como identificar o jogador. Por padrão enviamos o e-mail verificado da conta TopIdle. Se o seu jogo usa outro identificador — nick, ID de conta —, defina um rótulo e o jogador informa esse valor na hora de votar.
  4. Teste antes de publicar. O painel envia um evento vote.test real para o seu endpoint e mostra a resposta HTTP.

A integração não muda a posição do jogo no ranking. Ela existe para você premiar o jogador dentro do seu mundo — e é o que faz a comunidade voltar a votar todo dia.

Webhook de voto

Assim que um voto é confirmado, enviamos um POST para o endereço que você cadastrou. É o caminho recomendado: a recompensa chega em segundos, sem você precisar consultar nada.

POST https://seu-jogo.com/seu-endpoint assinado com HMAC-SHA256

Requisitos do endereço

Cabeçalhos que enviamos

CabeçalhoConteúdo
X-TopIdle-SignatureAssinatura no formato sha256=<hex>. Confira sempre.
X-TopIdle-TimestampMomento do envio, em segundos Unix. Entra no cálculo da assinatura.
X-TopIdle-EventIdentificador único do evento.
Idempotency-KeyMesmo valor do evento, para você descartar repetições.
X-TopIdle-Vote-IdMesmo valor do evento. Mantido por compatibilidade.
User-AgentTopIdle Webhook/1.0

Corpo do evento

Quando o jogo usa o e-mail da conta TopIdle como identificador:

{
  "type": "vote.created",
  "eventId": "evt_8Kd2mQ1xTn9r",
  "gameId": "seu-jogo-a1b2c3",
  "votedAt": "2026-09-01T14:32:07Z",
  "email": "[email protected]"
}

Quando você definiu um identificador próprio, o campo email é substituído por playerIdentifier — e nenhum e-mail é enviado:

{
  "type": "vote.created",
  "eventId": "evt_8Kd2mQ1xTn9r",
  "gameId": "seu-jogo-a1b2c3",
  "votedAt": "2026-09-01T14:32:07Z",
  "playerIdentifier": "NickDoJogador"
}

O teste do painel usa a mesma estrutura, com type igual a vote.test e um eventId começando em test_. Trate-o como um voto de mentira: útil para validar o caminho, não para creditar prêmio.

O que responder

Qualquer status 2xx significa recebido. Responda rápido: o tempo limite é de 5 segundos. Se o seu processamento for demorado, grave o evento e devolva 200 na hora, processando depois.

Conferir a assinatura

A assinatura garante que o evento veio do TopIdle e não foi alterado no caminho. Ela é o HMAC-SHA256 de {timestamp}.{corpo}, usando o segredo do webhook como chave, onde o corpo é o texto exatamente como recebido — não o resultado de decodificar e recodificar o JSON.

Node.js

import crypto from "node:crypto";

// Guarde o corpo cru: em Express, use express.raw({ type: "application/json" }).
function topIdleVoteIsValid(rawBody, headers, secret) {
  const timestamp = headers["x-topidle-timestamp"];
  const received = String(headers["x-topidle-signature"] || "");
  if (!timestamp || !received.startsWith("sha256=")) return false;

  // Rejeita eventos velhos: protege contra reenvio de um evento capturado.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(received.slice(7), "utf8");
  const b = Buffer.from(expected, "utf8");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

PHP

<?php
$raw = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_TOPIDLE_TIMESTAMP'] ?? '';
$received = $_SERVER['HTTP_X_TOPIDLE_SIGNATURE'] ?? '';
$secret = getenv('TOPIDLE_WEBHOOK_SECRET');

if ($timestamp === '' || strpos($received, 'sha256=') !== 0) {
    http_response_code(400);
    exit;
}
if (abs(time() - (int) $timestamp) > 300) {   // evento velho demais
    http_response_code(400);
    exit;
}

$expected = hash_hmac('sha256', $timestamp . '.' . $raw, $secret);
if (!hash_equals($expected, substr($received, 7))) {
    http_response_code(401);
    exit;
}

$evento = json_decode($raw, true);
// ... credite a recompensa, sem repetir o mesmo eventId
http_response_code(200);
!

Compare sempre com uma função de tempo constante — timingSafeEqual no Node, hash_equals no PHP. Comparar com == abre espaço para descobrir a assinatura byte a byte.

O segredo fica só no seu servidor. Nunca no cliente do jogo, em repositório público ou em log.

Reentrega e idempotência

Se o seu endpoint não responder 2xx, tentamos de novo com intervalos crescentes: 1 minuto, 5 minutos, 30 minutos, 2 horas, 6 horas, 12 horas e 24 horas. Depois disso o evento é marcado como falho e fica registrado no painel, onde você pode reenviar manualmente.

Por causa disso, o mesmo evento pode chegar mais de uma vez — por exemplo, se você processou o voto mas a resposta se perdeu. Guarde o eventId e ignore o que já viu. Sem isso, um jogador pode receber o mesmo prêmio duas vezes.

Falhas seguidas do seu endpoint aparecem no painel com o motivo (erro HTTP, certificado inválido, tempo esgotado) e avisamos a equipe por e-mail. É o primeiro lugar para olhar quando os jogadores dizem que não receberam.

API de consulta

Se você prefere buscar em vez de receber, ou quer conferir um jogador na hora em que ele abre o jogo, use os endpoints abaixo. A autenticação é a chave da integração, no cabeçalho Authorization:

Authorization: Bearer sua_chave_da_integracao
GET /api/v1/votes?after=<cursor> chave da integração

Lista os votos do seu jogo em ordem crescente, até 100 por chamada. Comece com after=0, guarde o nextCursor devolvido e use ele na próxima consulta. Cada item tem o mesmo formato do evento do webhook.

{
  "events": [
    {
      "type": "vote.created",
      "eventId": "evt_8Kd2mQ1xTn9r",
      "gameId": "seu-jogo-a1b2c3",
      "votedAt": "2026-09-01T14:32:07Z",
      "email": "[email protected]"
    }
  ],
  "nextCursor": 4821
}
GET /api/v1/[email protected] chave da integração

Diz se um jogador específico já votou e quando poderá votar de novo. Se o seu jogo usa identificador próprio, troque email por playerIdentifier.

{
  "hasVoted": true,
  "canVote": false,
  "lastVoteAt": "2026-09-01T14:32:07Z",
  "nextVoteAt": "2026-09-02T02:32:07Z",
  "cooldownSeconds": 43200
}

O cooldownSeconds acompanha o plano do jogo: 24 horas no gratuito, 18 no Light, 12 no Pro e 6 no Master. Use o valor devolvido em vez de fixar um número no código — assim a contagem no jogo continua certa se o plano mudar.

Dados públicos do jogo

GET /api/v1/games/<id> sem autenticação

Posição, votos e avaliações de qualquer jogo do ranking. Serve para mostrar no seu site ou no Discord quantos votos faltam para subir de posição.

{
  "id": "seu-jogo-a1b2c3",
  "name": "Seu Jogo Idle",
  "category": "Poke Idle",
  "status": "Lançado",
  "url": "https://topidle.com/jogo/seu-jogo-a1b2c3",
  "position": 4,
  "monthlyVotes": 137,
  "dailyVotes": 22,
  "likes": 281,
  "ratingCount": 167,
  "communityRating": { "average": 4.3 }
}

A média da comunidade só aparece a partir de cinco avaliações; antes disso o campo vem como null. Os votos do mês recomeçam no dia 1º.

Limites e erros

Os endpoints autenticados aceitam 120 requisições por minuto por chave. Ao passar disso a resposta é 429 com o cabeçalho Retry-After indicando quantos segundos esperar.

StatusQuando acontece
400Parâmetro ausente ou inválido — cursor não numérico, e-mail malformado.
401Chave ausente, inválida ou de uma integração desativada.
404Jogo não encontrado ou fora do ranking.
429Passou do limite por minuto. Espere o Retry-After.

Erros vêm sempre como JSON com um campo error em português, pronto para registrar no seu log.

Dúvidas frequentes

Preciso da integração para aparecer no ranking?

Não. Qualquer jogo entra no ranking e recebe votos sem integrar nada. A integração serve para você recompensar quem votou.

Posso premiar sem pedir o e-mail do jogador?

Pode, e é o mais comum. Defina um identificador próprio no painel — nick, ID de conta, o que o seu jogo já usa — e o jogador informa esse valor ao votar. Nesse modo o e-mail dele nunca é enviado.

O que acontece se meu servidor cair?

Nada se perde. Tentamos reentregar por até 24 horas e o painel guarda o histórico com o motivo da falha. Você também pode recuperar tudo depois pelo /api/v1/votes, que devolve os votos desde o cursor que você guardou.

A recompensa pode ser obrigatória para jogar?

Não condicione o acesso ao jogo a votar. A recompensa deve ser um bônus, não um pedágio — e o valor e as regras dela são responsabilidade do seu jogo, conforme os Termos de Uso.

Como faço para trocar o segredo do webhook?

No painel, em Integração. Ao gerar um novo segredo, o anterior deixa de valer imediatamente — atualize o seu servidor antes de girar a chave.

Falar com a equipe ↗