Pular para o conteúdo

Notificações

Quando alguém responde um comentário, o autor recebe uma notificação. O Qorum guarda essas notificações, e o seu site pode mostrá-las no widget, no próprio sistema de notificação dele, ou nos dois.

Quando uma notificação nasce

Tipos

typeQuando
replyResponderam um comentário da pessoa.
mentionMarcaram a pessoa com @usuario. Quem já foi avisado pela resposta não recebe duas.
followComentário novo numa conversa que a pessoa segue. Enquanto ela não lê, os avisos da mesma conversa viram um só, com count somando ("3 comentários novos"). O webhook sai só no primeiro de cada leva.

Ninguém recebe aviso de quem bloqueou.

Como ela chega

O mesmo formato em todos os lugares: na API, na gerência, no feed e no webhook.

{
  "id": 318,
  "type": "reply",
  "userId": 77,                       // quem recebe (id no seu site, ou "q:N" para conta Qorum)
  "read": false,
  "createdAt": "2026-11-08T21:00:00Z",
  "actorId": 123,                     // quem respondeu
  "actor": { "id": 123, "username": "ana_lima", "displayName": "Ana Lima", "avatar": "https://..." },
  "comment": { "id": 82, "html": "Concordo, e...", "preview": "Concordo, e..." },   // a resposta
  "inReplyTo": { "id": 81, "preview": "Acho que o melhor caminho é..." },        // o comentário de quem recebe
  "thread": { "ref": "post-42", "title": "Meu post", "url": "https://meusite.com/post-42" },
  "link": "https://meusite.com/post-42#qorum-82",   // abre a página já na resposta
  "commentId": 82, "preview": "Concordo, e..."      // atalhos (iguais a comment.id e comment.preview)
}

Com isso dá para montar a frase completa: "Ana Lima respondeu ao seu comentário 'Acho que o melhor caminho é...' em Meu post", com link direto para a resposta. O link só existe se a conversa tem url (o widget manda sozinho).

Quatro jeitos de usar

JeitoPara quêVocê faz
Sino do widgetMostrar dentro da área de comentáriosNada. Já vem pronto.
API com o login da pessoaUm sino no topo do seu site ou no seu aplicativoChamar do navegador com o mesmo token do widget
API do seu servidorMostrar ou marcar como lida do seu backendChamar com a chave secreta, informando o usuário
Webhook e feedCopiar para o sistema de notificação que o seu site já temReceber o aviso na hora e, por garantia, puxar o feed

Com o login da pessoa

As mesmas rotas que o sino do widget usa. Mande a chave pública e o token da pessoa, igual à API pública.

GET/v1/notifications
Parâmetro
unread=1Só as não lidas.
limitQuantas, de 1 a 100. Padrão: 30.
beforePara paginar: o valor de next da resposta anterior.
{ "notifications": [ ... ], "unread": 3, "next": 290 }   // next: null quando acabou
GET/v1/notifications/count

Só o número de não lidas, leve para checar de tempos em tempos: {"unread": 3}.

POST/v1/notifications/read

{"ids": [318, 317]} marca essas; corpo vazio marca todas. Devolve quantas foram marcadas em marked.

Exemplo: um sino no topo do seu site

// usa o mesmo token do login do seu site que você passa ao widget
async function qorum(caminho, opcoes = {}) {
  const r = await fetch('https://useqorum.com' + caminho, {
    ...opcoes,
    headers: { 'X-Qorum-Key': 'pk_SUA_CHAVE_PUBLICA', Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' },
  });
  return r.json();
}

async function atualizarSino() {
  const { unread } = await qorum('/v1/notifications/count');
  document.querySelector('#sino .numero').textContent = unread || '';
}

async function abrirSino() {
  const { notifications } = await qorum('/v1/notifications?limit=20');
  lista.innerHTML = '';
  for (const n of notifications) {
    const item = document.createElement('a');
    item.href = n.link || '#';
    item.className = n.read ? '' : 'nova';
    item.textContent = `${n.actor.displayName || n.actor.username} respondeu: "${n.preview}"`;
    lista.appendChild(item);
  }
  await qorum('/v1/notifications/read', { method: 'POST', body: '{}' });
  atualizarSino();
}

setInterval(atualizarSino, 60000);   // a cada minuto
atualizarSino();

Este pedido vem do navegador, então a página precisa estar num domínio liberado do site, como o widget.

Pelo seu servidor

Com a chave pública e a secreta, como na API de gerência. Você informa de quem são as notificações no endereço.

GET/v1/site/users/:userId/notifications

Os mesmos parâmetros e a mesma resposta de /v1/notifications.

GET/v1/site/users/:userId/notifications/count
POST/v1/site/users/:userId/notifications/read
// a pessoa leu as notificações no seu site: marque no Qorum também, para o sino do widget concordar
await gerencia('POST', '/users/77/notifications/read', { ids: [318, 317] });

:userId é o id da pessoa no seu site, o mesmo sub do token. Para contas Qorum, use q:N.

Copiar para o sistema de notificação do seu site

Se o seu site já tem notificações (um sino, e-mail, push no celular), copie as do Qorum para lá. O jeito mais seguro usa as duas coisas juntas:

  1. Webhook para chegar na hora

    O evento notification.created chega no instante em que a resposta aparece, com a notificação inteira em notification.

  2. Feed para não perder nenhuma

    De tempos em tempos (ou ao ligar o servidor), peça ao feed tudo o que veio depois da última notificação que você guardou. Ele cobre o que se perdeu se o seu servidor ficou fora do ar.

  3. Guarde pelo id

    O id é único: se a mesma notificação chegar pelo webhook e pelo feed, grave uma vez só.

GET/v1/site/notifications?since=290&limit=100

Todas as notificações do site depois do id since, da mais antiga para a mais nova (até 500 por pedido). userId diz quem recebe cada uma.

{ "notifications": [ { "id": 291, "userId": 77, ... }, { "id": 292, "userId": 15, ... } ],
  "last": 292,      // guarde e mande em since no próximo pedido
  "more": false }   // true: ainda tem mais, peça de novo

Exemplo completo (Node)

// 1) na hora, pelo webhook (veja a conferência da assinatura em /docs/webhooks)
async function processar(aviso) {
  if (aviso.event === 'notification.created') await guardar(aviso.notification);
}

// 2) por garantia, a cada 5 minutos, pelo feed
async function sincronizar() {
  let desde = await ultimoIdGuardado();          // 0 na primeira vez
  for (;;) {
    const r = await gerencia('GET', `/notifications?since=${desde}&limit=500`);
    for (const n of r.notifications) await guardar(n);
    desde = r.last;
    if (!r.more) break;
  }
}
setInterval(sincronizar, 5 * 60 * 1000);

// grava uma vez só por id e entrega no sistema do seu site
async function guardar(n) {
  const novo = await db.query(
    'INSERT INTO notificacoes_qorum (id, usuario, dados) VALUES ($1, $2, $3) ON CONFLICT (id) DO NOTHING RETURNING id',
    [n.id, String(n.userId), n]);
  if (!novo.rowCount || String(n.userId).startsWith('q:')) return;   // repetida, ou conta Qorum (não existe no seu banco)
  await notificarUsuario(n.userId, {
    titulo: `${n.actor.displayName || n.actor.username} respondeu você`,
    texto: n.preview,
    link: n.link,
  });
}

O comentário respondido

Em inReplyTo vem o comentário da pessoa que recebeu a resposta, com um trecho do texto. Se esse comentário foi apagado, inReplyTo vem null, mas a notificação continua valendo enquanto a resposta existir.

O webhook comment.created também traz replyToCommentId e notifyUserId. Se você só quer saber quem avisar, ele basta. O notification.created é mais prático porque já vem pronto para mostrar.