XTREMETOP300

API de recompensas por voto

Dale a tus jugadores una recompensa dentro del juego por votar. Hay dos formas de recibir los votos, las dos con una sola clave y las dos pensadas para funcionar en un servidor detrás de NAT o que se reinicia dos veces al día.

¿Cuál uso?

Pull — empieza por aquí

Tu servidor nos pregunta cada uno o dos minutos qué recompensas debe entregar. No necesita puertos abiertos, ni dominio, ni certificado TLS. Funciona desde una conexión doméstica.

Push — si tienes un endpoint web

Hacemos un POST a tu URL en cuanto llega un voto, firmado para que puedas comprobar que somos nosotros. Es instantáneo, pero solo si podemos llegar a tu servidor.

Puedes usar los dos. Una recompensa enviada a tu callback se puede seguir reclamando por pull, así que un push perdido nunca es una recompensa perdida.

1. Consigue tu clave

Abre tu ficha en Mis servidores → Recompensas por voto, activa las recompensas y copia la clave de la API. Envíala como cabecera en cada llamada:

Authorization: Bearer YOUR_API_KEY

Nunca pongas la clave en una URL. Acaba en los logs de acceso, en los logs del proxy y en el historial del navegador.

2. Consulta qué recompensas debes

POST /api/v1/rewards/pending devuelve las recompensas sin reclamar, de la más antigua a la más reciente.

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 no consume nada. Entrega las recompensas y después confírmalas:

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 qué dos llamadas? Si tu servidor se cae entre la consulta y la entrega, la recompensa sigue ahí en el siguiente ciclo. Reclamarla es lo que dice “el jugador ya la tiene”. Si prefieres hacer una sola llamada, envía "claim": true con la petición de pendientes — recibes las recompensas y se marcan como entregadas al instante, pero una caída en el peor momento hace que se pierdan.

3. O simplemente pregunta sí o no

La mayoría de los plugins solo quieren saber si deben recordarle a alguien que vote al entrar. GET /api/v1/rewards/check responde por nombre de jugador o por dirección 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, si lo quieres al instante

Configura una URL de callback en los ajustes de recompensas y le enviaremos esto por POST cada vez que llegue un voto:

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": "..." }
}

Responde con cualquier estado 2xx. Cualquier otra respuesta se reintenta 5 veces durante la hora siguiente, y después la recompensa simplemente espera a que la recojas por pull. Un 4xx se trata como un rechazo y no se reintenta, así que no devuelvas uno por un problema temporal.

5. Verifica la firma

Cualquiera puede hacer un POST a tu URL de callback. La firma es lo que distingue un voto real de un desconocido regalándose diamantes. Calcula HMAC-SHA256(timestamp + "." + rawBody) con tu clave de la API y compáralo con la cabecera.

// 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. Envía a los jugadores un enlace que ya los reconoce

Añade el nombre del jugador al enlace de votación y el campo llega ya rellenado, así que solo tiene que hacer clic. Sirve cualquiera de player, pingUsername, username o nick — los adicionales existen para que un botón que ya hiciste para otro ranking pueda apuntar aquí sin cambios.

https://xtremetop300.com/vote/1234?player=Steve

Reglas que conviene conocer

  • Solo se crea una recompensa por un voto que se ha contado. Los votos que marcan nuestras comprobaciones antifraude no ganan nada, así que una granja de votos no puede llevarse tu moneda del juego dejando intacto tu ranking.
  • Un voto por jugador cada 24 horas, el mismo límite que el propio ranking.
  • Trata cada token como entregado al menos una vez. Guarda los que ya has pagado e ignora los repetidos. Es lo único que un plugin tiene que hacer bien sí o sí.
  • Nunca metas un nombre de jugador directamente en un comando de consola. Limitamos los nombres a letras, números, espacios, puntos, guiones y guiones bajos, pero tu servidor debería comprobar igualmente que el nombre corresponde a una cuenta real antes de pagar.
  • 50 recompensas por pull de forma predeterminada, 200 como máximo. Fíjate en el campo more y vuelve a llamar si es true.
  • 60 peticiones por minuto por ficha. Consultar una vez por minuto gasta una de ellas.
  • Las recompensas no recogidas se eliminan al cabo de 14 días.

Errores

401
Falta la clave o es incorrecta. Revisa el prefijo Bearer.
403
La clave es correcta, pero la ficha no está publicada o las recompensas están desactivadas.
422
Falta un parámetro o tiene un formato incorrecto; el cuerpo de la respuesta indica cuál.
429
Demasiadas peticiones. Espera un poco y vuelve a intentarlo — no se pierde nada.

¿Ya tienes ficha? Abre los ajustes de tu servidor para activar las recompensas. ¿Todavía no la tienes? Añade tu servidor.