Artigos sobre: Websites & APIs
Este artigo também está disponível em:

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 (formato t=<timestamp>,v1=<assinatura>), em que a assinatura é um HMAC SHA-256 de <timestamp>.<corpo bruto> feito com o seu segredo whs_. O webhook legado, que enviava uma senha no cabeçalho Authorization, não é coberto aqui.



1. Como Funciona a Comunicação?


  1. O usuário vota no Top⁠.gg.
  2. O Top⁠.gg envia uma requisição JSON assinada para a URL da sua aplicação.
  3. 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:


  1. Deploy: Envie o seu código (lembre-se de não enviar node_modules ou venv).
  2. 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.
  3. Subdomínio: Defina um nome (ex.: topgg-webhook). A URL do seu webhook será topgg-webhook⁠.squareweb⁠.app/webhook, com HTTPS.
  4. Variáveis de Ambiente: Adicione a chave TOPGG_WEBHOOK_SECRET com o segredo whs_ gerado pelo Top⁠.gg (próximo passo).



5. Configuração no Painel do Top⁠.gg


  1. Abra o painel do seu projeto no Top⁠.gg e vá em Webhooks.
  2. Cole a URL do seu webhook e salve.
  3. O Top⁠.gg gera um segredo que começa com whs_: copie-o para a variável de ambiente TOPGG_WEBHOOK_SECRET na Square Cloud e mantenha-o em sigilo.
  4. 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 (204 ou 200). 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

Este artigo foi útil?

Compartilhe seu feedback

Cancelar

Obrigado!