Webhook das notas recebidas
O webhook avisa sua aplicação quando a captura encontra uma nota recebida nova ou quando uma nota recebida é cancelada.
Headers, chave e assinatura são diferentes dos do webhook de emissão. Código escrito para o webhook de emissão não confere estes avisos.
| Emissão | Notas recebidas | |
|---|---|---|
| Onde se configura | webhook_url em cada emissão | uma vez, por empresa |
| Headers | X-Qualyfiscal-* | X-Conota-* |
| O que é assinado | o corpo | <timestamp>.<corpo> |
| Chave | fornecida pelo Conota | uma por conta, gerada por você no painel ou pela API |
| Tentativas | 3 | 8, ao longo de ~22 h |
Configurar
O endereço precisa estar cadastrado antes, no painel, em Integração → Destinos de webhook, no ambiente produção. Depois, ligue para a empresa:
curl -X PUT https://api.conota.dev/v1/captura/webhook \
-H "X-API-Key: $CONOTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "cpf_cnpj": "11222333000181", "url": "https://seu-sistema.com.br/conota/notas-recebidas" }'
- Endereço fora dos destinos autorizados de produção:
422. - Valem as mesmas exigências de endereço do webhook de emissão:
https://na porta 443, endereço público, nome de host, sem redirecionamento. "ativo": falsedesliga.
Na primeira configuração da conta, a resposta traz a chave de assinatura:
{
"cpf_cnpj": "11222333000181",
"webhook": { "url": "https://seu-sistema.com.br/conota/notas-recebidas", "ativo": true },
"chave_de_assinatura": { "valor": "csec_...", "criada_em": "2026-09-25T19:40:00.000Z" }
}
Guarde-a num gerenciador de segredos. Ela assina os avisos de todas as empresas da conta. Se
perder, gere outra com POST /v1/captura/webhook/segredo — a troca vale na hora, então atualize seu
sistema logo em seguida.
Quando chega um aviso
| Evento | Quando |
|---|---|
nota_recebida | a captura encontrou uma nota recebida nova |
nota_recebida_cancelada | uma nota recebida foi cancelada pelo emissor |
Não geram aviso: o histórico baixado ao ligar a captura, as notas que a própria empresa emitiu e tudo que chegar enquanto o webhook estiver desligado.
POST /conota/notas-recebidas
Content-Type: application/json
X-Conota-Evento: nota_recebida
X-Conota-Entrega: 5b0f3c1e-0000-4000-8000-000000000000
X-Conota-Tentativa: 1
X-Conota-Timestamp: 1790364130
X-Conota-Signature: sha256=<hmac_hex>
{
"evento": "nota_recebida",
"entrega_id": "5b0f3c1e-0000-4000-8000-000000000000",
"ocorrido_em": "2026-09-25T19:22:10.000Z",
"empresa": { "cpf_cnpj": "11222333000181" },
"nota": {
"tipo": "nfse",
"chave": "35503081299888777000166000000000012326081234567895",
"papel": "tomador",
"numero": "123",
"emissor": { "cpf_cnpj": "99888777000166", "nome": "PRESTADORA EXEMPLO LTDA" },
"valor": 1500,
"data_emissao": "2026-09-20T13:00:00.000Z",
"situacao": "autorizada",
"municipio_incidencia": "3550308",
"xml_integral": true
},
"xml_url": "https://api.conota.dev/v1/captura/notas/xml?chave=35503081299888777000166000000000012326081234567895&cpf_cnpj=11222333000181"
}
O XML não vem no aviso: xml_url aponta para a rota autenticada, com a sua chave de API.
Conferir a assinatura
const crypto = require('crypto')
// corpoBruto: o body EXATAMENTE como chegou, antes do JSON.parse
function avisoValido(corpoBruto, headers, chave) {
const ts = headers['x-conota-timestamp']
// Aviso com mais de 5 minutos pode ser um reenvio capturado no caminho.
if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
const esperado = 'sha256=' + crypto
.createHmac('sha256', chave)
.update(`${ts}.${corpoBruto}`)
.digest('hex')
const a = Buffer.from(esperado)
const b = Buffer.from(headers['x-conota-signature'] || '')
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
JSON.parse seguido de JSON.stringify pode mudar a ordem e o espaçamento — e a assinatura deixa de
bater. Guarde o body cru antes do parse.
Entrega e repetição
- Responda 2xx, rápido. Sem 2xx, tentamos de novo em 1 min, 5 min, 15 min, 1 h, 3 h, 6 h e 12 h — 8 tentativas ao todo.
- O mesmo aviso pode chegar mais de uma vez. Use
X-Conota-Entrega(igual aentrega_id) para descartar o que já processou. - Os avisos pendentes vão para o endereço configurado na hora da entrega: se você trocar o endereço, eles seguem para o novo; se desligar o webhook ou tirar o destino da lista de autorizados, deixam de ser enviados.
Se o seu endpoint ficar fora do ar durante todas as tentativas, o aviso se perde. Mantenha uma rotina
que consulte GET /v1/captura/notas com desde e compare com o que você já tem. Webhook é o caminho
rápido; a consulta é a rede de segurança.