Webhooks
Receba avisos em tempo real quando uma conversa passa para humano, um lead aparece ou um agendamento é pedido.
Com webhooks, a Nevox avisa o seu sistema quando algo acontece. É assim que uma conversa que precisa de humano aparece direto no seu painel administrativo, sem ninguém abrir a Nevox.
Cadastrar um endpoint
Pelo painel
Não precisa de chave de API.
- Entre na Nevox e confira, no seletor de empresas, se a empresa aberta é a certa.
- Vá em Desenvolvedores → Webhooks → Adicionar endpoint.
- Cole a URL do seu sistema que vai receber os avisos (precisa começar com
https://). - Escolha o ambiente (Produção para dados reais) e marque os eventos.
- Salve e copie o segredo
whsec_…: ele aparece uma única vez. Perdeu? Apague o endpoint e cadastre de novo, ou gere outro pela API (rotate-secret, abaixo).
Na mesma tela ficam as últimas entregas de cada endpoint, com Reenviar, e o botão para ligar e desligar. Para mandar um aviso de teste, use POST /v1/webhooks/{id}/test (abaixo).
Pela API
POST /v1/webhooks · escopo webhooks:write
curl https://api.nevox.ai/v1/webhooks \
-H "Authorization: Bearer $NEVOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.suaempresa.com/webhooks/nevox",
"events": ["conversation.handoff", "conversation.closed", "lead.created"],
"description": "Fila de atendimento do admin"
}'{
"id": "whk_6tq2",
"url": "https://api.suaempresa.com/webhooks/nevox",
"events": ["conversation.handoff", "conversation.closed", "lead.created"],
"status": "enabled",
"secret": "whsec_...",
"created_at": "2026-11-03T17:20:00Z"
}O secret aparece uma única vez. Outros endpoints: GET /v1/webhooks, PATCH /v1/webhooks/{id}, DELETE /v1/webhooks/{id}, POST /v1/webhooks/{id}/rotate-secret, POST /v1/webhooks/{id}/test.
Use "events": ["*"] para receber todos. O endpoint fica no ambiente da chave que o criou: endpoint criado com chave de teste recebe só os eventos de conversas de teste. A URL precisa usar https e não pode apontar para endereço de rede interna.
POST /v1/webhooks/{id}/test envia na hora um evento webhook.test só para aquele endpoint e devolve o resultado.
Formato do evento
POST /webhooks/nevox HTTP/1.1
Content-Type: application/json
Nevox-Signature: t=1762183200,v1=9ab3...
Nevox-Event-Id: evt_1z8x
Nevox-Environment: live{
"id": "evt_1z8x",
"type": "conversation.handoff",
"created_at": "2026-11-03T17:20:00Z",
"data": {
"conversation": {
"id": "conv_7mk2p9",
"agent_id": "agt_8f2k1x",
"channel": "whatsapp",
"status": "handoff",
"summary": "Paciente pede reembolso da consulta de 28/10; diz que não conseguiu entrar na chamada.",
"reason": "refund_request"
},
"contact": {
"id": "ctc_4hq81",
"external_id": "paciente_1042",
"name": "Maria Souza"
}
}
}A assinatura usa o mesmo formato das ações: HMAC-SHA256 de "{t}.{corpo bruto}" com o secret do endpoint.
Eventos
| Evento | Quando |
|---|---|
conversation.created | Nova conversa, em qualquer canal |
conversation.handoff | O agente passou para humano (motivo em reason) |
conversation.human_assigned | Alguém da equipe respondeu pela primeira vez |
conversation.closed | Conversa encerrada |
message.created | Nova mensagem (alto volume; use só se precisar espelhar o chat) |
contact.created / contact.updated | Contato criado ou com dados alterados |
lead.created (em breve) | Um visitante ou contato foi qualificado como lead |
appointment.requested (em breve) | O agente coletou um pedido de agendamento |
knowledge.ready / knowledge.failed (em breve) | Arquivo ou URL terminou de processar |
learning.suggestion_created (beta) | Nova sugestão de aprendizado |
visitor.identified | Um visitante anônimo foi ligado a um contato |
analytics.export_ready | Uma exportação terminou |
form.submitted | Alguém enviou um formulário de um site da empresa feito pela Nevox |
Valores de reason em conversation.handoff: complaint, refund_request, special_request, forbidden_topic, user_asked_human, low_confidence, action_failed, manual.
Formulários dos sites
Quando a Nevox faz o site da empresa, cada formulário enviado nele (contato, orçamento, cadastro) vira o evento form.submitted e fica na lista GET /v1/form_submissions, no mesmo formato. Assine o evento para receber na hora; use a lista para buscar o histórico ou conferir se perdeu algum.
{
"id": "evt_9c1e0b7a4f2d4e8a9b3c5d6e7f8a9b0c",
"type": "form.submitted",
"created_at": "2026-10-28T14:05:12Z",
"data": {
"form_submission": {
"id": "frm_3f9c2a7e0b1d4c5e8f6a9b0c1d2e3f40",
"site": { "id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "name": "Site TAZZ", "domain": "tazz.app" },
"form": "Cadastro de motorista",
"name": "Maria Souza",
"email": "maria@exemplo.com",
"phone": "(11) 98765-4321",
"phone_e164": "+5511987654321",
"message": "Quero dirigir aos fins de semana.",
"fields": {
"Cidade": "São Paulo",
"Página": "/motoristas",
"Idioma": "pt-BR",
"Veio de": "https://www.google.com/",
"utm_source": "instagram",
"utm_medium": "bio",
"utm_campaign": "motoristas-outubro"
},
"page": "/motoristas",
"referrer": "https://www.google.com/",
"language": "pt-BR",
"utm": { "source": "instagram", "medium": "bio", "campaign": "motoristas-outubro", "term": null, "content": null },
"created_at": "2026-10-28T14:05:12Z"
}
}
}phonevem como a pessoa escreveu;phone_e164só quando dá para ter certeza do país (o número começa com+ou tem o tamanho de um número do país do site). Senão,null.fieldstraz todos os outros campos do formulário, com o rótulo do site, mais página, idioma, origem eutm_*.page,referrer,languageeutmrepetem esses valores para você não precisar lerfields.- Só formulários do ambiente
live. O evento sai uma vez por envio; oiddo formulário (frm_…) serve para não gravar duas vezes.
Passo a passo para receber os formulários
- Cadastre um endpoint em Desenvolvedores → Webhooks com o evento
form.submitted, no ambiente Produção (como cadastrar). Guarde o segredowhsec_…no seu sistema. - No seu sistema, confira a assinatura de cada aviso (como conferir), grave o formulário pelo
id(frm_…) e responda2xx. - Envie um formulário de verdade no site: ele deve aparecer no seu sistema em segundos, e a entrega aparece em Últimas entregas no painel.
- Para trazer os formulários enviados antes disso, use Gerar chave em Desenvolvedores → Chaves de API com uma chave de Produção só com o escopo
forms:reade use a lista abaixo.
O segredo e a chave dão acesso a nome, telefone e email de quem preencheu: guarde no servidor, nunca no navegador nem em mensagem aberta. Vazou? Troque o segredo (apague e recadastre o endpoint, ou rotate-secret) e revogue a chave.
Buscar o histórico
curl "https://api.nevox.ai/v1/form_submissions?form=Cadastro%20de%20motorista&created_after=2026-10-01T00:00:00Z&limit=50" -H "Authorization: Bearer $NEVOX_SECRET_KEY"{
"data": [ { "id": "frm_3f9c2a7e0b1d4c5e8f6a9b0c1d2e3f40", "form": "Cadastro de motorista", "name": "Maria Souza", "...": "..." } ],
"has_more": true,
"next_cursor": "MTIz"
}Escopo forms:read. Mais novos primeiro, até 100 por página; para a próxima, mande cursor com o next_cursor. Filtros: site_id (o id do site), form (o nome exato do formulário) e created_after (data ISO 8601).
Entrega e novas tentativas
- Responda com qualquer
2xxem até 10 segundos. Processe o evento depois, numa fila. - Sem
2xx, a Nevox tenta de novo em 1 min, 5 min, 30 min, 2 h, 6 h, 12 h e 24 h. - Depois de 3 dias falhando, o endpoint é desativado e o dono da conta recebe um email.
- A ordem de entrega não é garantida. Use
created_ate busque o estado atual pela API se precisar. - O mesmo evento pode chegar mais de uma vez. Use o
iddo evento para ignorar repetidos.
As últimas entregas aparecem em Painel → Desenvolvedores → Webhooks, com botão para reenviar. O histórico fica guardado por 30 dias.
O summary da conversa pode vir null no evento: o resumo é gerado logo depois e fica em GET /v1/conversations/{id}.
Exemplo: fila de atendimento no seu admin
// app/api/webhooks/nevox/route.ts (Next.js)
import { assinaturaValida } from "@/lib/nevox";
export async function POST(req: Request) {
const raw = await req.text();
const ok = assinaturaValida(
raw,
req.headers.get("nevox-signature") ?? "",
process.env.NEVOX_WEBHOOK_SECRET!
);
if (!ok) return new Response("assinatura inválida", { status: 400 });
const evento = JSON.parse(raw);
if (evento.type === "conversation.handoff") {
await db.ticket.upsert({
where: { nevoxConversationId: evento.data.conversation.id },
create: {
nevoxConversationId: evento.data.conversation.id,
pacienteId: evento.data.contact.external_id,
resumo: evento.data.conversation.summary,
motivo: evento.data.conversation.reason,
},
update: {},
});
}
return new Response("ok");
}