Payloads de webhook

Configure destinos, valide a assinatura e conheça o payload de cada evento que o Mira envia.

O Mira envia eventos JSON para destinos HTTPS cadastrados por projeto. O receptor não precisa de conta nem de chave de API do Mira: o segredo do destino autentica cada mensagem.

Configurar um destino

Quem faz: admin do projeto.

Abrir Webhooks

Em Configurações do projeto, abra Integrações e clique em Webhooks. O painel lateral lista os destinos.

Adicionar o destino

Clique em Adicionar destino e informe nome, URL HTTPS pública e os eventos que o destino recebe.

Guardar o segredo

Salve e copie o segredo (mira_whsec_...) para a configuração privada do receptor. Ele aparece uma vez e não pode ser consultado depois.

Testar

Use Enviar teste e abra Histórico de entregas para ver o resultado. O teste publica webhook.test só para esse destino, com data.report: null.

A URL precisa usar HTTPS com certificado válido. O Mira bloqueia HTTP, endereços privados, loopback e redes especiais, inclusive quando o DNS resolve para elas. O Mira não segue redirects e recusa URLs com usuário, senha ou fragmento. Não coloque segredos na URL. O envio espera até dez segundos pelos headers da resposta.

Eventos

EventoQuandoDados adicionais
report.createdNovo report confirmadoSnapshot do report
report.updatedAlteração efetiva de campos ou tagsSnapshot final e changes, com before e after por campo
comment.createdComentário adicionadoSnapshot do report e o comment
attachment.createdLink ou arquivo adicionadoSnapshot do report e o attachment
error.createdPrimeira ocorrência de um erro novoSnapshot do erro em error
error.regressedErro resolvido volta a não resolvido numa release com build posteriorSnapshot do erro em error
error.spikePico de ocorrências marcado no erroSnapshot do erro em error
digest.dailyTodo dia às 09:00 no fuso configurado, se o projeto tem reports paradosResumo em data.digest
digest.weeklySegunda-feira, quando o resumo semanal do projeto é gravadoResumo em data.digest, com kind: "weekly"
webhook.testTeste administrativoreport nulo e changes vazio

Os eventos error.*, digest.daily e digest.weekly só chegam a destinos que os marcam na configuração. No formulário, os digests aparecem como Digest diário e Resumo semanal. Um mesmo erro publica error.spike no máximo uma vez por hora.

Uma atualização de vários campos produz um único report.updated. Repetir uma criação idempotente, ou atualizar com os mesmos valores, não publica evento. Renomear uma tag não publica alteração de report. Excluir uma tag em uso publica report.updated para os reports afetados.

Envelope

Todo evento tem o mesmo envelope:

{
  "id": "5f0b9d0e-3c1a-4b8e-9a53-0c4f6a7d2e11",
  "type": "report.updated",
  "version": 1,
  "occurredAt": "2026-11-04T14:32:10.000Z",
  "projectId": "b7c4f2d8-1e6a-4f0c-8d3b-9a2e5c7f1a40",
  "actor": { "id": "…", "name": "Ana Souza", "email": "ana@empresa.com" },
  "data": {
    "report": { "id": "…", "number": 42, "type": "bug", "title": "…", "status": "triaged" },
    "changes": {
      "status": { "before": "new", "after": "triaged" },
      "priority": { "before": null, "after": "high" }
    }
  }
}
CampoConteúdo
idUUID do evento. É o mesmo em todas as tentativas
typeUm dos tipos da tabela acima
versionSempre 1
occurredAtData ISO 8601
projectIdUUID do projeto
actorQuem causou o evento, com id, name e email. Nulo nos eventos error.* e digest.*
datareport, changes e, conforme o tipo, comment, attachment, error ou digest

Aceite campos novos dentro do contrato v1 e confira type antes de interpretar data.

Snapshot do report

data.report traz id, projectId, number, type, title, description, priority, status, reporter, assignee, environmentId, environment, pageUrl, tags, attachments, createdAt e updatedAt. Cada item de attachments traz id, kind, name, contentType, sizeBytes, url (só em links) e createdAt. O snapshot não leva o contexto técnico, a metadata dos anexos nem o conteúdo dos arquivos.

Campos de texto e URLs podem conter dados pessoais digitados por quem reportou. Restrinja o acesso e a retenção no receptor.

Ao marcar um duplicado, changes inclui duplicateOfId e o novo status.

Eventos de comentário e anexo

comment.created acrescenta data.comment com id, reportId, author, body, origin, agentBadge, parentId, createdAt e editedAt. attachment.created acrescenta data.attachment com os campos de anexo descritos acima.

Eventos de erro

Nos eventos error.*, actor e data.report são nulos, changes é vazio e data.error traz o estado do erro:

{
  "id": "…",
  "type": "error.created",
  "version": 1,
  "occurredAt": "2026-11-04T14:40:02.000Z",
  "projectId": "…",
  "actor": null,
  "data": {
    "report": null,
    "changes": {},
    "error": {
      "id": "…",
      "environmentId": "…",
      "fingerprint": "…",
      "type": "TypeError",
      "message": "Cannot read properties of undefined (reading 'total')",
      "culprit": "src/checkout/summary.ts in computeTotal",
      "level": "error",
      "status": "unresolved",
      "eventCount": 1,
      "userCount": 1,
      "firstSeenAt": "2026-11-04T14:40:02.000Z",
      "lastSeenAt": "2026-11-04T14:40:02.000Z",
      "firstRelease": "1.4.0+abc1234",
      "lastRelease": "1.4.0+abc1234",
      "resolvedAt": null,
      "spikeDetectedAt": null,
      "regressedAt": null
    }
  }
}

O snapshot também inclui projectId, resolvedBuildTime e regressionEnabled.

Digests

digest.daily e digest.weekly trazem data.digest, com:

  • kind (daily ou weekly), generatedAt, timezone, project (id, key, name) e url do projeto.
  • counts com os totais de aging (envelhecendo), unassigned (sem responsável) e urgentStalled (urgentes parados).
  • as listas aging, unassigned e urgentStalled, com até 20 reports cada, onde cada item traz number, title, type, priority, assignee, createdAt, lastActivityAt e url.
  • No semanal, period com start e end (a semana anterior, de segunda a segunda) e summary com body, model e period.

O digest diário só sai quando o projeto tem ao menos um report em alguma das três listas. O semanal sai quando o resumo escrito pelo assistente é gravado, mesmo com as listas vazias.

Verificar a assinatura

Cada POST inclui:

Content-Type: application/json
Mira-Webhook-Id: UUID do evento
Mira-Webhook-Timestamp: Unix timestamp em segundos
Mira-Webhook-Signature: v1=HMAC hexadecimal

A chave é o segredo completo (mira_whsec_...), usado como string UTF-8. O HMAC usa SHA-256 sobre id.timestamp.corpo, onde corpo são os bytes exatos recebidos, antes de qualquer parse. Durante uma rotação de segredo, o header traz duas assinaturas separadas por vírgula. Aceite a mensagem se uma delas bater.

import { createHmac, timingSafeEqual } from "node:crypto";

function verifyWebhook(
  secret: string,
  id: string,
  timestamp: string,
  signatures: string,
  rawBody: Buffer,
): boolean {
  if (!/^\d+$/.test(timestamp)) return false;
  const seconds = Number(timestamp);
  if (!Number.isSafeInteger(seconds)) return false;
  if (Math.abs(Date.now() / 1000 - seconds) > 300) return false;
  const expected = createHmac("sha256", secret)
    .update(`${id}.${timestamp}.`)
    .update(rawBody)
    .digest();
  return signatures.split(",").some((entry) => {
    const candidate = entry.trim();
    if (!/^v1=[a-f0-9]{64}$/.test(candidate)) return false;
    return timingSafeEqual(Buffer.from(candidate.slice(3), "hex"), expected);
  });
}

Tentativas e histórico

Cada ciclo tem seis tentativas: a inicial e cinco retries, depois de 1 minuto, 5 minutos, 15 minutos, 1 hora e 6 horas. Qualquer resposta fora de 2xx, falha de DNS, conexão, TLS ou timeout conta como falha. O Mira registra início, conclusão, status HTTP, duração e um código de erro de cada tentativa, mas não o corpo nem os headers da resposta.

As entregas podem se repetir e chegar fora de ordem. Não há garantia de execução exatamente uma vez no receptor, então trate o id como chave de idempotência. A nova tentativa usa o mesmo id e o mesmo corpo, com timestamp e assinatura novos.

Depois da última falha, Reenviar inicia outro ciclo. Não há reenvio de entrega já confirmada. O histórico, com payload e tentativas, é visível só para admins. Pausar um destino cancela as pendências, e reativar não reproduz o que aconteceu durante a pausa. Excluir impede novos envios e preserva o histórico.

Rotação do segredo

Em Webhooks, gere um novo segredo. A chave nova aparece uma vez. Atualize o receptor em até 15 minutos: durante essa janela o Mira assina com a chave nova e a anterior, e depois usa só a nova. Outra rotação fica bloqueada até o fim da janela.

Próximos passos

Nesta página