Voltar pros Docs

Webhooks

Configure URLs externas que recebem POST em tempo real quando eventos importantes acontecem no seu servidor: raid detectado, ticket aberto, membro entrou, etc. Tudo assinado com HMAC SHA-256.

Quickstart em 3 passos

  1. 1
    Configure o webhook no painel: entre em /dashboard/[seu_servidor]/webhooks, clique "Adicionar webhook", cole a URL do seu receiver, escolha os eventos.
  2. 2
    Anote o secret que aparece (1 vez só — guarde como variável de ambiente no seu receiver).
  3. 3
    Implemente validação HMAC + processamento. Exemplos abaixo. Clica em "Testar" no painel pra confirmar que tá tudo conectado.

Headers da requisição

HeaderDescrição
Content-TypeSempre application/json
User-AgentGBBOT-Webhooks/1.0
X-GBBOT-Signaturesha256=<hex> — HMAC SHA-256 da body
X-GBBOT-EventNome do evento (ex: raid.detected)
X-GBBOT-Delivery-IdUUID único — use pra idempotência

Validar a assinatura

SEMPRE valide a assinatura antes de processar — sem isso qualquer um pode fingir ser o GBBOT e mandar payload falso pro seu endpoint.

Node.js (Express)

javascript
// Express + raw body
const express = require('express');
const crypto = require('crypto');
const app = express();

// IMPORTANTE: precisa do body BRUTO (string) antes de parsear
app.post('/webhooks/gbbot',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const SECRET = process.env.GBBOT_WEBHOOK_SECRET;
    const rawBody = req.body.toString('utf8');

    // Calcula HMAC esperado
    const expected = crypto
      .createHmac('sha256', SECRET)
      .update(rawBody)
      .digest('hex');

    // Pega o que o GBBOT enviou
    const received = (req.headers['x-gbbot-signature'] || '')
      .replace('sha256=', '');

    // timingSafeEqual evita timing attack
    const ok = received.length === expected.length &&
      crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(received)
      );

    if (!ok) return res.status(401).end();

    // Parse + processa
    const payload = JSON.parse(rawBody);
    console.log('Evento:', payload.event, payload.data);

    res.status(200).end();
  }
);

app.listen(3000);

Python (Flask)

python
# Flask
from flask import Flask, request, abort
import hmac, hashlib, os

app = Flask(__name__)
SECRET = os.environ['GBBOT_WEBHOOK_SECRET'].encode()

@app.post('/webhooks/gbbot')
def gbbot_webhook():
    body = request.get_data()  # bytes brutos, ANTES de parsear

    expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
    received = request.headers.get(
        'X-GBBOT-Signature', ''
    ).replace('sha256=', '')

    if not hmac.compare_digest(expected, received):
        abort(401)

    payload = request.get_json()
    print(f"Evento: {payload['event']}", payload['data'])
    return '', 200

Referência de eventos (9)

Cada evento tem `event`, `guildId`, `deliveryId`, `timestamp` e um `data` específico.

raid.detected

Anti-raid expulsou ou baniu uma conta suspeita.

json
{
  "event": "raid.detected",
  "guildId": "1234567890",
  "deliveryId": "abc-uuid",
  "timestamp": "2026-05-20T15:30:00.000Z",
  "data": {
    "action": "ban",                    // 'ban' ou 'kick'
    "reason": "conta_nova",
    "accountAgeDays": 2,
    "minAccountAgeDays": 7,
    "member": {
      "id": "999999",
      "tag": "spammer#0000",
      "username": "spammer"
    }
  }
}

punishment.applied

AutoMod aplicou punição (ban/kick/mute/timeout). Não inclui "warn" (que só apaga msg).

json
{
  "event": "punishment.applied",
  "guildId": "1234567890",
  "deliveryId": "abc-uuid",
  "timestamp": "2026-05-20T15:30:00.000Z",
  "data": {
    "action": "timeout",                // timeout/mute/kick/ban
    "reason": "AutoMod: Flood/Spam detectado",
    "durationMins": 10,                 // null se kick/ban
    "member": {
      "id": "999999",
      "tag": "user#0000",
      "username": "user"
    }
  }
}

ticket.created

Cliente abriu ticket de suporte com guildId associado.

json
{
  "event": "ticket.created",
  "guildId": "1234567890",
  "deliveryId": "abc-uuid",
  "timestamp": "2026-05-20T15:30:00.000Z",
  "data": {
    "ticketId": "65f8a1b2c3d4e5f6a7b8c9d0",
    "subject": "Dúvida sobre boas-vindas",
    "category": "support",
    "userId": "123456789",
    "userTag": "fernando#1234"
  }
}

ticket.closed

Admin fechou um ticket (transição open→closed).

json
{
  "event": "ticket.closed",
  "guildId": "1234567890",
  "deliveryId": "abc-uuid",
  "timestamp": "2026-05-20T15:30:00.000Z",
  "data": {
    "ticketId": "65f8...",
    "subject": "...",
    "category": "support",
    "userId": "123456789",
    "closedBy": "987654321",
    "closedReason": "Resolvido."
  }
}

member.joined

Novo membro entrou (e PERMANECEU — se anti-raid expulsou, dispara raid.detected em vez disso).

json
{
  "event": "member.joined",
  "guildId": "1234567890",
  "deliveryId": "abc-uuid",
  "timestamp": "2026-05-20T15:30:00.000Z",
  "data": {
    "member": {
      "id": "555",
      "tag": "novo#0001",
      "username": "novo",
      "avatar": "https://cdn.discordapp.com/avatars/...",
      "accountAgeDays": 120
    },
    "memberCount": 1234
  }
}

member.left

Membro saiu (kick ou voluntária — não distinguimos no webhook).

json
{
  "event": "member.left",
  "guildId": "1234567890",
  "deliveryId": "abc-uuid",
  "timestamp": "2026-05-20T15:30:00.000Z",
  "data": {
    "member": { "id": "555", "tag": "saiu#0001", "username": "saiu" },
    "memberCount": 1233
  }
}

license.expiring

Plano vence em 7, 1 ou 0 dias. Disparado pelo sweeper diário na janela 09:00–10:00.

json
{
  "event": "license.expiring",
  "guildId": "1234567890",
  "deliveryId": "abc-uuid",
  "timestamp": "2026-05-20T09:30:00.000Z",
  "data": {
    "guildName": "Meu Servidor",
    "expiresAt": "2026-05-27T23:59:59.000Z",
    "daysUntilExpiration": 7,
    "phase": "D-7",                     // D-7, D-1 ou D-0
    "plan": "elite"
  }
}

license.expired

Plano expirou ontem (D+1). Dispara 1 vez.

json
{
  "event": "license.expired",
  "guildId": "1234567890",
  "deliveryId": "abc-uuid",
  "timestamp": "2026-05-20T09:30:00.000Z",
  "data": {
    "guildName": "Meu Servidor",
    "expiresAt": "2026-05-19T23:59:59.000Z",
    "daysUntilExpiration": -1,
    "phase": "D+1",
    "plan": "elite"
  }
}

test

Disparado pelo botão "Testar" no painel. Pra validação inicial do receiver.

json
{
  "event": "test",
  "guildId": "1234567890",
  "deliveryId": "abc-uuid",
  "timestamp": "2026-05-20T15:30:00.000Z",
  "data": {
    "triggeredBy": "admin-discord-id",
    "message": "Webhook de teste do GBBOT. Se você está vendo isso, o receiver tá funcionando."
  }
}

Boas práticas

  • Idempotência via X-GBBOT-Delivery-IdEm caso de retry (timeout/5xx), o GBBOT reenvia o MESMO deliveryId. Guarde em cache/Redis e dropa duplicado.
  • Responda rápido (< 5 segundos)Timeout do dispatcher é 5s. Processamento longo? Responda 200 OK e processa async (fila/worker).
  • Retries automáticosGBBOT retenta 1× em caso de timeout ou 5xx, com 2 segundos de backoff. Após 10 falhas seguidas, o webhook é AUTO-DESATIVADO — você reativa no painel.
  • Não confie em ordem de eventosWebhooks podem chegar fora de ordem em raros casos (rede). Use timestamps no payload pra ordenar se precisar.
  • Rotacione o secret periodicamentePainel tem botão "Rotacionar secret" — gera novo, invalida o antigo. Atualize seu receiver no momento da rotação.

Receitas comuns

📢 Postar no Slack

Recebe o webhook do GBBOT, valida HMAC, formata como Slack message e POST no Slack incoming webhook. ~30 linhas em Node.

🔌 Integrar via Zapier / n8n

Use o "Webhook by Zapier" como trigger. NÃO permite validar HMAC nativamente — recomenda-se autenticar com URL secreta (`?token=`) como camada adicional.

📊 Salvar em BigQuery / Postgres

Recebe + valida + INSERT INTO eventos. Útil pra analytics agregada por guilda ao longo do tempo.

🚨 Alertar no Telegram

Filtra só raid.detected e license.expiring, manda mensagem na sua DM do Telegram via Bot API.

Tem alguma dúvida ou achou um bug nessa doc?

Nos fala no Discord ou abre um ticket.