KanutusDocs
kanutus.com Entrar / Criar contaEntrar

Webhooks

O Kanutus manda um POST HTTPS para o seu servidor quando algo acontece: uma sala começou, uma reunião foi agendada, uma sessão de agente terminou, um limite de gasto foi atingido.

Crie um endpoint

No app: Desenvolvedores → Webhooks → Adicionar (no modo Teste para endpoints de teste). Ou pela API, com a permissão webhooks:write:

bash
curl -X POST https://api.kanutus.com/v1/webhook-endpoints \
  -H "Authorization: Bearer $KANUTUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://seu-sistema.com/kanutus", "events": ["meeting.created", "booking.created", "session.ended"]}'

A resposta traz o segredo de assinatura whsec_…. Ele aparece uma única vez: guarde-o junto com os seus outros segredos.

A URL precisa ser https e pública, sem redirecionamento. Endereços privados, de loopback e de metadados de nuvem são recusados.

O evento

json
{
  "id": "evt_8c1Zq…",
  "type": "booking.created",
  "created": 1791668000,
  "livemode": true,
  "org_id": "…",
  "api_version": "v1",
  "data": { "object": { "id": "…" } }
}

Os eventos levam ids e só o mínimo necessário; os números de telefone vão mascarados. Texto de transcrição nunca vai no evento: busque-o com a sua credencial.

Confira a assinatura

Toda requisição traz o cabeçalho Kanutus-Signature:

text
Kanutus-Signature: t=1791668000,v1=5f2b…

v1 é o HMAC-SHA256, em hexadecimal, de "<t>.<corpo bruto>" calculado com o seu segredo. Para conferir:

  • calcule sobre o corpo bruto, antes de interpretar o JSON;
  • compare em tempo constante;
  • recuse carimbos de tempo com mais de 5 minutos.

Durante a troca de segredo, o mesmo cabeçalho traz dois valores v1= por 24 horas. Aceite a requisição se qualquer um deles bater.

Node.js
import crypto from "node:crypto";

export function verifyKanutus(rawBody, header, secret, toleranceS = 300) {
  const parts = header.split(",").map((p) => p.trim().split("="));
  const t = Number(parts.find(([k]) => k === "t")?.[1]);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceS) return false;
  const want = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return parts.some(([k, v]) => k === "v1" && v.length === want.length &&
    crypto.timingSafeEqual(Buffer.from(v), Buffer.from(want)));
}

Responda rápido; o Kanutus tenta de novo

  • Responda com qualquer 2xx em até 10 segundos e deixe o trabalho pesado para depois (numa fila).
  • Qualquer outra resposta é reenviada com espera exponencial: 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h, 24 h (cerca de 3 dias).
  • Um endpoint que falha sem parar por 3 dias é desativado, e o Dono recebe um e-mail.
  • A ordem dos eventos não é garantida, e o mesmo evento pode chegar duas vezes: use o id para descartar repetidos.
  • Perdeu algum evento? GET /v1/events lista os eventos dos últimos 30 dias. No app, dá para ver cada entrega e reenviar.
  • Para trocar o segredo, use POST /v1/webhook-endpoints/{id}/rotate-secret. Para mandar um teste assinado, use POST /v1/webhook-endpoints/{id}/test.

Tipos de evento

EventoQuando
room.startedUma sala foi criada pela API
room.ended, room.participant_joined, room.participant_leftCiclo de vida da sala Em breve
session.started, session.endedUm agente de IA entrou ou saiu de uma sala
meeting.created, meeting.canceledUma reunião foi marcada ou cancelada
meeting.updatedUma reunião mudou Em breve
booking.createdUm horário foi agendado num link de agendamento
booking.rescheduled, booking.canceledMudanças num agendamento Em breve
transcript.readyUma transcrição já pode ser lida
summary.readyUm resumo já pode ser lido Em breve
call.blockedUma ligação foi recusada (destino ou finalidade não permitidos)
call.ringing, call.answered, call.voicemail, call.completed, call.failedAndamento da ligação Em breve
voice.consent_completed, voice.ready, voice.failedClonagem de voz com consentimento Em breve
usage.thresholdUma chave chegou a 80% ou 100% do limite de gasto
credential.expiringUma chave vai vencer em breve Em breve