Pular para o conteúdo

Webhooks

Avisos que o Qorum manda para o seu servidor quando algo acontece nos comentários. Use para dar pontos a quem comenta, notificar respostas no seu site, avisar a equipe sobre denúncias ou guardar um registro.

Configurar

  1. Crie a rota no seu servidor

    Uma rota que aceita POST com JSON, como https://meusite.com/qorum/webhook. Ela precisa ser pública e responder em até 10 segundos.

  2. Informe o endereço no painel

    Em Configurações → Webhook, ou com PUT /v1/site/settings e {"webhookUrl": "..."}.

  3. Pegue o segredo

    Em Configurações → Chaves, o "Segredo do webhook" (wh_...). Guarde no servidor, como a chave secreta.

  4. Teste

    Em Testar conexão → Webhook, clique em Enviar ping. O resultado mostra o código que o seu servidor respondeu e quanto tempo levou.

Como chega

POST /qorum/webhook
Content-Type: application/json
User-Agent: Qorum-Webhook/1
X-Qorum-Event: comment.created
X-Qorum-Delivery: 1532
X-Qorum-Timestamp: 1794171698
X-Qorum-Signature: sha256=5d41402abc4b2a76b9719d911017c592...

{ "event": "comment.created", "site": "meu-blog", "sentAt": "2026-11-08T21:00:00Z", ... }
CabeçalhoO que é
X-Qorum-EventO tipo do aviso, o mesmo do campo event.
X-Qorum-DeliveryO número do aviso. Se o mesmo número chegar duas vezes (num reenvio), ignore o segundo.
X-Qorum-TimestampQuando foi enviado, em segundos desde 1970.
X-Qorum-SignatureA assinatura, para você conferir que veio do Qorum. Veja abaixo.

Eventos

comment.created

Um comentário passou a aparecer: na hora em que foi escrito, ou quando foi aprovado na fila.

{
  "event": "comment.created", "site": "meu-blog", "sentAt": "2026-11-08T21:00:00Z",
  "comment": { "id": 82, "parentId": 81, "content": "Concordo!", "html": "Concordo!", "images": null, "createdAt": "..." },  // images: lista quando tem imagem
  "thread": { "ref": "post-42", "title": "Meu post", "url": "https://meusite.com/post-42" },
  "userId": "123",            // quem escreveu (id no seu site, ou q:N)
  "isReply": true,
  "replyToUserId": "77",      // só em respostas: quem foi respondido
  "replyToCommentId": 81,     // só em respostas: o comentário que foi respondido
  "notifyUserId": "77",       // quem você deve notificar. Nunca é o próprio autor.
  "preview": "Concordo!"      // até 100 caracteres
}

notification.created

Alguém recebeu uma resposta, foi marcado com @ ou tem comentário novo numa conversa que segue (veja o campo type: reply, mention ou follow). Chega junto com o comment.created, com a notificação pronta para mostrar, no mesmo formato da API. Veja Notificações.

{ "event": "notification.created", "site": "meu-blog", "sentAt": "...",
  "notification": { "id": 318, "type": "reply", "userId": "77", "actor": { ... }, "comment": { ... },
                    "inReplyTo": { "id": 81, "preview": "..." }, "thread": { ... }, "link": "https://...#qorum-82" } }

comment.deleted

Um comentário publicado foi apagado. Comentários recusados na fila não geram este aviso, porque nunca chegaram a aparecer.

{ "event": "comment.deleted", "site": "meu-blog", "sentAt": "...",
  "commentId": 81, "parentId": null, "userId": "123",
  "byModerator": true, "moderatorId": "q:5",
  "removedReplies": 2 }       // respostas apagadas junto

comment.restored

Um comentário apagado voltou (pelo painel ou pela API de gerência). Se você desconta algo no comment.deleted, devolva aqui.

{ "event": "comment.restored", "site": "meu-blog", "sentAt": "...",
  "commentId": 81, "parentId": null, "userId": "123",
  "restoredReplies": 2 }      // respostas que voltaram junto

comment.reported

{ "event": "comment.reported", "site": "meu-blog", "sentAt": "...",
  "reportId": 5, "commentId": 81, "reason": "Spam ou propaganda",
  "reporterId": "77", "authorId": "123", "preview": "texto denunciado", "threadRef": "post-42" }

ping

Só o botão de teste manda este. Responda 200 e pronto.

{ "event": "ping", "site": "meu-blog", "sentAt": "..." }

Conferir a assinatura

Qualquer um pode mandar um POST para a sua rota. A assinatura prova que o aviso veio do Qorum e não foi alterado:

  1. Leia o corpo cru

    Exatamente os bytes recebidos, antes de converter o JSON. Converter e montar de novo muda os bytes e a assinatura não bate.

  2. Calcule o HMAC

    HMAC-SHA256 com o segredo do webhook, sobre timestamp + "." + corpo. Escreva em hexadecimal e coloque sha256= na frente.

  3. Compare

    Com o cabeçalho X-Qorum-Signature, usando comparação de tempo constante.

  4. Confira a hora

    Recuse se o X-Qorum-Timestamp tiver mais de 5 minutos de diferença do seu relógio. Isso impede que alguém reenvie um aviso antigo.

const crypto = require('crypto');

app.post('/qorum/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.get('X-Qorum-Timestamp') || '';
  const esperado = 'sha256=' + crypto.createHmac('sha256', process.env.QORUM_WEBHOOK_SECRET)
    .update(ts + '.').update(req.body).digest('hex');
  const recebido = req.get('X-Qorum-Signature') || '';
  const ok = recebido.length === esperado.length &&
    crypto.timingSafeEqual(Buffer.from(recebido), Buffer.from(esperado)) &&
    Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
  if (!ok) return res.sendStatus(401);

  const aviso = JSON.parse(req.body);
  res.sendStatus(200);           // responda logo e processe depois
  processar(aviso).catch(console.error);
});

Respostas, demora e reenvio

Exemplo: pontos e notificação

async function processar(aviso) {
  if (await jaProcessado(aviso)) return;                  // guarde o X-Qorum-Delivery ou o id do comentário

  if (aviso.event === 'comment.created') {
    await darPontos(aviso.userId, 5);                     // quem comentou ganha pontos no seu site
    if (aviso.notifyUserId) {
      await notificar(aviso.notifyUserId, `Responderam você em "${aviso.thread.title}"`,
                      aviso.thread.url + '#qorum-' + aviso.comment.id);
    }
  }
  if (aviso.event === 'comment.deleted') {
    await tirarPontos(aviso.userId, 5);
  }
  if (aviso.event === 'comment.reported') {
    await avisarEquipe(`Denúncia: ${aviso.reason} em ${aviso.threadRef}`);
  }
}

Lembre: userId é o id que o seu site mandou no login. Para contas Qorum, começa com q: e não existe no seu banco.

Quando não chega