Como criar um webhook do Top.gg
Como Criar um Webhook do Top.gg na Square Cloud
Os webhooks são fundamentais para integrar seu bot com serviços externos. Sempre que um usuário vota no seu bot, o Top.gg envia uma requisição POST para a sua aplicação. Na Square Cloud, você pode hospedar esse webhook de forma estável na porta 80.
Este guia usa os webhooks v1 do Top.gg. Cada requisição vem assinada no cabeçalho
x-topgg-signature(formatot=<timestamp>,v1=<assinatura>), em que a assinatura é um HMAC SHA-256 de<timestamp>.<corpo bruto>feito com o seu segredowhs_. O webhook legado, que enviava uma senha no cabeçalhoAuthorization, não é coberto aqui.
1. Como Funciona a Comunicação?
- O usuário vota no Top.gg.
- O Top.gg envia uma requisição JSON assinada para a URL da sua aplicação.
- Sua aplicação recalcula a assinatura com o seu segredo, rejeita o que não bater e processa o voto (
data.user.platform_idé o ID do Discord de quem votou).
2. Implementação em JavaScript (Express)
Instale o Express e ative a sintaxe import:
npm pkg set type=module
npm install express
O exemplo verifica a assinatura com o módulo nativo crypto do Node, sem nenhum pacote do Top.gg (nos nossos testes, o listener do @top-gg/sdk 4.0.0 respondeu entregas válidas com timeout, o que faz o Top.gg reenviá-las).
import crypto from "node:crypto";
import express from "express";
const app = express();
// O segredo whs_ da página Webhooks do seu projeto
const SECRET = process.env.TOPGG_WEBHOOK_SECRET;
function isValidSignature(rawBody, header = "") {
// Formato do cabeçalho: t=<timestamp unix>,v1=<HMAC-SHA256 em hex de "<timestamp>.<corpo bruto>">
const parts = Object.fromEntries(header.split(",").map((item) => item.split("=")));
const timestamp = Number(parts.t);
if (!SECRET || !Buffer.isBuffer(rawBody) || !parts.v1 || !timestamp) return false;
// Rejeita requisições com mais de 5 minutos (proteção contra replay)
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = crypto.createHmac("sha256", SECRET).update(`${parts.t}.`).update(rawBody).digest();
const received = Buffer.from(parts.v1, "hex");
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
// express.raw mantém exatamente os bytes que foram assinados
app.post("/webhook", express.raw({ type: "*/*" }), (req, res) => {
if (!isValidSignature(req.body, req.get("x-topgg-signature"))) return res.sendStatus(401);
const { type, data } = JSON.parse(req.body);
// "vote.create" é um voto real; "webhook.test" vem do botão Send Test
if (type === "vote.create" || type === "webhook.test") {
console.log(`Voto recebido! ID do usuário: ${data.user.platform_id}`);
}
res.sendStatus(204);
});
// A Square Cloud define PORT=80
const port = Number(process.env.PORT) || 80;
app.listen(port, "0.0.0.0", () => console.log(`Ouvindo na porta ${port}`));
3. Implementação em Python (Flask)
Instale as bibliotecas com pip install flask waitress (o Waitress é um servidor de produção; não use o servidor de desenvolvimento do Flask).
import hashlib
import hmac
import json
import os
import time
from flask import Flask, abort, request
from waitress import serve
app = Flask(__name__)
# O segredo whs_ da página Webhooks do seu projeto
SECRET = os.environ.get("TOPGG_WEBHOOK_SECRET", "")
def is_valid_signature(raw_body: bytes, header: str) -> bool:
# Formato do cabeçalho: t=<timestamp unix>,v1=<HMAC-SHA256 em hex de "<timestamp>.<corpo bruto>">
try:
parts = dict(item.split("=", 1) for item in header.split(","))
timestamp = parts["t"]
signature = parts["v1"]
age = abs(time.time() - int(timestamp))
except (KeyError, ValueError):
return False
if age > 300: # rejeita requisições com mais de 5 minutos (proteção contra replay)
return False
message = timestamp.encode() + b"." + raw_body
expected = hmac.new(SECRET.encode(), message, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected.encode(), signature.encode())
@app.route("/webhook", methods=["POST"])
def topgg_webhook():
raw_body = request.get_data()
if not SECRET or not is_valid_signature(raw_body, request.headers.get("x-topgg-signature", "")):
abort(401)
payload = json.loads(raw_body)
# "vote.create" é um voto real; "webhook.test" vem do botão Send Test
if payload["type"] in ("vote.create", "webhook.test"):
print(f'Voto recebido! ID do usuário: {payload["data"]["user"]["platform_id"]}')
return "", 204
if __name__ == "__main__":
# A Square Cloud define PORT=80
serve(app, host="0.0.0.0", port=int(os.environ.get("PORT", 80)))
4. Configuração na Square Cloud
Para o webhook funcionar, você deve configurar a sua aplicação como um site:
- Deploy: Envie o seu código (lembre-se de não enviar
node_modulesouvenv). - Publicação na Web: No formulário de envio, ative a opção "Publicação na Web" já no primeiro envio e defina o subdomínio (ou use
SUBDOMAIN=no arquivo de configuração). Sites precisam de pelo menos 512 MB de RAM. - Subdomínio: Defina um nome (ex.:
topgg-webhook). A URL do seu webhook serátopgg-webhook.squareweb.app/webhook, com HTTPS. - Variáveis de Ambiente: Adicione a chave
TOPGG_WEBHOOK_SECRETcom o segredowhs_gerado pelo Top.gg (próximo passo).
5. Configuração no Painel do Top.gg
- Abra o painel do seu projeto no Top.gg e vá em Webhooks.
- Cole a URL do seu webhook e salve.
- O Top.gg gera um segredo que começa com
whs_: copie-o para a variável de ambienteTOPGG_WEBHOOK_SECRETna Square Cloud e mantenha-o em sigilo. - Clique em Send Test e confira se o voto aparece nos logs da sua aplicação.
6. Boas Práticas
- Segurança: Nunca exponha o segredo no código. Use sempre variáveis de ambiente.
- Logs: Mantenha logs das requisições recebidas para depurar caso um voto não seja processado.
- Tratamento de Erros: Responda sempre rápido (
204ou200). Se a lógica de recompensa for demorada, processe-a de forma assíncrona para evitar timeout, o que faz o Top.gg reenviar a entrega.
Para mais detalhes, veja o tutorial de Top.gg da Square Cloud.
Atualizado em: 04/10/2026
Obrigado!
