Ir para o conteúdo
Documentação

Webhooks

A MadNotas avisa o seu sistema a cada mudança importante com um POST em JSON, assinado com HMAC-SHA256. Cadastre os endereços em Integração › Webhooks ou pela API.

O que chega

POST https://seu-sistema.exemplo/madnotas (dados fictícios)
MadNotas-Assinatura: t=1761757392,v1=5f0c1e9a8b…
MadNotas-Evento: nfe.autorizada
MadNotas-Evento-Id: evt_01jbx9q2w3e4r5t6y7u8i9o0pa
MadNotas-Entrega-Id: 18342
MadNotas-Tentativa: 1
Content-Type: application/json

{
  "id": "evt_01jbx9q2w3e4r5t6y7u8i9o0pa",
  "tipo": "nfe.autorizada",
  "criado_em": "2026-10-29T14:03:12-03:00",
  "ambiente": "producao",
  "api_versao": "v1",
  "dados": {
    "id": "nfe_01jbx4k2m9q7t3v8w5z6y1r0ac",
    "ref": "pedido-1042",
    "status": "autorizada"
  }
}

O campo dados traz o mesmo objeto que GET /nfe/{id} devolveria. Use id do evento para ignorar repetições: a mesma entrega pode chegar mais de uma vez.

Como responder

  • Responda 2xx em até 10 segundos. Processe o trabalho pesado depois, numa fila sua.
  • Qualquer outro status, timeout ou erro de rede vira nova tentativa: 1 min, 5 min, 15 min, 1 h, 3 h, 6 h, 12 h, 24 h e 24 h (10 tentativas, cerca de 3 dias).
  • Responder 410 desliga o endpoint na hora. Sem nenhuma entrega com sucesso por 3 dias, ele também é desligado e a conta recebe e-mail.
  • Redirecionamentos não são seguidos. Em produção (e no endpoint de todos os ambientes), só https://; em simulação e homologação, http:// também vale.

Toda entrega fica listada por 30 dias em Integração › Webhooks › Entregas, com a resposta do seu servidor e o botão de reenvio.

Verificar a assinatura

O cabeçalho MadNotas-Assinatura tem o formato t=<unix>,v1=<hex>. Para conferir:

  1. Separe t e os valores v1 (pode haver dois durante a troca de segredo).
  2. Calcule HMAC-SHA256(segredo, t + "." + corpo) sobre o corpo bruto, antes de qualquer parse de JSON.
  3. Compare em tempo constante com cada v1; basta um bater.
  4. Recuse se t estiver a mais de 5 minutos do seu relógio (proteção contra reenvio malicioso).
function madnotasAssinaturaValida(string $cabecalho, string $corpo, string $segredo, int $tolerancia = 300): bool
{
    $t = null;
    $assinaturas = [];
    foreach (explode(',', $cabecalho) as $parte) {
        [$nome, $valor] = array_pad(explode('=', trim($parte), 2), 2, '');
        if ($nome === 't' && ctype_digit($valor)) {
            $t = (int) $valor;
        } elseif ($nome === 'v1') {
            $assinaturas[] = strtolower($valor);
        }
    }
    if ($t === null || ! $assinaturas || abs(time() - $t) > $tolerancia) {
        return false;
    }
    $esperado = hash_hmac('sha256', $t . '.' . $corpo, $segredo);
    foreach ($assinaturas as $a) {
        if (hash_equals($esperado, $a)) {
            return true;
        }
    }
    return false;
}

// Laravel: use o corpo bruto
$ok = madnotasAssinaturaValida(
    $request->header('MadNotas-Assinatura', ''),
    $request->getContent(),
    env('MADNOTAS_WEBHOOK_SEGREDO')
);

Segredo e rotação

Cada endpoint tem um segredo whsec_…, revelado no painel. Ao rotacionar, o segredo anterior continua assinando junto com o novo por 24 horas (dois v1 no cabeçalho), tempo para você trocar no seu sistema sem perder entregas. Pela API, POST /webhooks/{id}/rotacionar-segredo devolve o segredo novo e segredo_anterior_valido_ate, a hora em que o antigo deixa de valer. Para remover um endpoint, DELETE /webhooks/{id} responde 200 com {"id": "whk_…", "excluido": true}.

Eventos

Inscreva o endpoint em eventos específicos, em grupos (nfe.*, nfce.*…) ou em todos (["*"]). A lista abaixo é a mesma que GET /webhooks/eventos devolve; evento fora dela é recusado com 422 evento_desconhecido, e lista ausente ou vazia, com 422 eventos_obrigatorio.

EventoGrupoQuando
nfe.autorizadaNF-eA SEFAZ autorizou a NF-e.
nfe.rejeitadaNF-eA SEFAZ rejeitou a NF-e (corrija e envie de novo com a mesma ref).
nfe.denegadaNF-eA NF-e foi denegada (irregularidade cadastral de emitente ou destinatário).
nfe.canceladaNF-eO cancelamento da NF-e foi homologado.
nfe.carta_correcaoNF-eUma carta de correção da NF-e foi registrada.
nfe.contingenciaNF-eA NF-e foi emitida em contingência (SVC) por indisponibilidade da SEFAZ.
nfe.evento_rejeitadoNF-eA SEFAZ rejeitou um evento da NF-e (cancelamento ou carta de correção); a nota continua como estava.
nfce.autorizadaNFC-eA SEFAZ autorizou a NFC-e.
nfce.rejeitadaNFC-eA SEFAZ rejeitou a NFC-e.
nfce.denegadaNFC-eA NFC-e foi denegada.
nfce.canceladaNFC-eO cancelamento da NFC-e foi homologado.
nfce.contingenciaNFC-eA NFC-e foi emitida em contingência offline e será transmitida quando a SEFAZ voltar.
nfce.evento_rejeitadoNFC-eA SEFAZ rejeitou um evento da NFC-e (cancelamento); a nota continua como estava.
nfse.autorizadaNFS-eA Sefin Nacional gerou a NFS-e.
nfse.rejeitadaNFS-eA Sefin Nacional recusou a DPS (corrija e envie de novo com a mesma ref).
nfse.canceladaNFS-eO cancelamento da NFS-e foi homologado (inclusive o feito fora do MadNotas).
nfse.substituidaNFS-eA NFS-e substituta foi autorizada e a nota antiga passou a substituída.
nfse.evento_rejeitadoNFS-eA Sefin Nacional recusou um evento da NFS-e (cancelamento); a nota continua como estava.
dfe.recebidoDF-e recebidoChegou uma NF-e emitida contra o seu CNPJ, ou uma que já estava aqui mudou: XML completo disponível ou evento do fornecedor (cancelamento, carta de correção).
dfe.manifestacao_registradaDF-e recebidoO Ambiente Nacional registrou a sua manifestação (ciência, confirmação, desconhecimento ou operação não realizada).
dfe.manifestacao_rejeitadaDF-e recebidoO Ambiente Nacional recusou a sua manifestação; a nota continua como estava.
inutilizacao.homologadaInutilizaçãoA SEFAZ homologou a inutilização da faixa de números.
inutilizacao.rejeitadaInutilizaçãoA SEFAZ rejeitou o pedido de inutilização.
emitente.certificado_vencendoEmitenteO certificado digital do emitente vence em 30, 15, 7 ou 1 dia (ou já venceu).
emitente.numeracao_conflitoEmitenteA SEFAZ (ou a Sefin, na NFS-e) já conhecia o número com outra nota; o documento foi renumerado.
emitente.nfse_convenio_alteradoEmitenteO município do emitente deixou de ser conveniado ao Sistema Nacional da NFS-e, ou voltou a ser.
emitente.numeracao_buracoEmitenteHá números sem uso na série de produção há mais de 48 horas; inutilize a faixa até o prazo.
emitente.contingencia_ativadaEmitenteO emitente passou a emitir em contingência (automática ou pedida).
emitente.contingencia_encerradaEmitenteO emitente voltou a emitir normalmente na SEFAZ.
sefaz.status_alteradoSEFAZO serviço da SEFAZ de uma UF que você usa ficou instável, caiu ou voltou.
conta.franquia_80ContaA empresa usou 80% da franquia de notas do mês.
conta.franquia_100ContaA franquia do mês acabou; as próximas notas em produção entram como excedente.

Os grupos nfse.* e dfe.* usam o mesmo endpoint e a mesma assinatura. Nos eventos nfse.*, dados é a NFS-e (e, no cancelamento, no cancelamento recusado e na substituição, um bloco a mais com o evento ou a nota substituta); veja o guia da NFS-e. Em dfe.recebido, dados traz o motivo (novo, xml_completo ou evento_emissor) e a nota recebida; veja o guia do DF-e.

Fora do catálogo existe só webhook.teste, enviado pelo botão Enviar teste (ou POST /webhooks/{id}/teste) a um único endpoint, para conferir a assinatura. Ele não é assinável: chega mesmo sem estar na lista de eventos do endpoint.

Números sem nota na produção

Quando sobra um número da série sem nota (uma emissão abandonada, por exemplo), a SEFAZ espera que ele seja inutilizado até o dia 10 do mês seguinte. A inutilização é um ato fiscal, então na produção a MadNotas não faz isso sem você autorizar:

  • Padrão (desligado): nada vai à SEFAZ. Com a faixa parada há 48 horas, você recebe emitente.numeracao_buraco, um aviso por faixa, e decide: inutilizar com POST /v1/inutilizacoes ou pela tela Notas › Inutilizar numeração, ou usar o número numa nota.
  • Ligado: em Emitentes › Inutilizar números parados, um processo diário inutiliza sozinho as faixas paradas há 48 horas. Cada uma aparece na lista de inutilizações com a origem automática, e o resultado chega por inutilizacao.homologada ou inutilizacao.rejeitada.
dados de emitente.numeracao_buraco (dados fictícios)
{
  "emitente_id": 12,
  "modelo": "55",
  "ambiente": "producao",
  "serie": 1,
  "numero_inicial": 1041,
  "numero_final": 1043,
  "quantidade": 3,
  "prazo_inutilizacao": "2026-11-10",
  "mensagem": "Os números 1041 a 1043 da série 1 estão sem nota. Inutilize até 10/11/2026."
}

Na simulação e na homologação, a numeração não tem efeito fiscal: a faixa parada é inutilizada sozinha, sem aviso.