Skip to main content

Webhooks

Webhooks são chamadas HTTP que a Engine API faz para sua URL quando um evento acontece — nota autorizada, rejeitada, certificado vencendo. Você não precisa fazer polling.
Webhooks chegam em menos de 2 segundos após o evento. Em caso de falha, há 5 retentativas automáticas com backoff exponencial.

Configurando um Webhook

curl -X POST https://api.engineapi.com.br/webhooks \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://seusite.com/webhooks/engine",
    "events": ["invoice.authorized", "invoice.rejected", "certificate.expiring_soon"],
    "secret": "meu-segredo-hmac"
  }'

Eventos disponíveis

EventoQuando dispara
invoice.authorizedNota fiscal autorizada pela SEFAZ
invoice.rejectedNota rejeitada pela SEFAZ
invoice.canceledNota cancelada
invoice.correctionCarta de correção emitida
mdfe.authorizedMDFe autorizado
mdfe.closedMDFe encerrado
nfse.authorizedNFSe autorizada pela prefeitura
certificate.expiring_soonCertificado vence em 30 dias
certificate.expiring_criticalCertificado vence em 7 dias
certificate.expiredCertificado expirou

Estrutura do Payload

{
  "id": "evt_01HX4Z9KBR5C7J8QNMF2DVWPG",
  "event": "invoice.authorized",
  "timestamp": "2026-04-26T23:00:00.000Z",
  "data": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "accessKey": "35260211222333000144550010000000011000000019",
    "status": "AUTHORIZED",
    "type": "NFE",
    "issuerId": "ISSUER_ID"
  }
}
O campo id do evento (evt_...) é único e pode ser usado para garantir idempotência — se o mesmo evento chegar duas vezes, ignore a duplicata.

Verificando a Autenticidade (HMAC)

A Engine API assina cada webhook com HMAC SHA-256 usando o secret que você forneceu. Sempre valide a assinatura antes de processar o evento. O header enviado é: X-EngineAPI-Signature: sha256=<hash>
import { createHmac, timingSafeEqual } from 'crypto';
import express from 'express';

const app = express();
app.use(express.raw({ type: 'application/json' }));

app.post('/webhooks/engine', (req, res) => {
  const signature = req.headers['x-engineapi-signature'] as string;
  const secret = process.env.WEBHOOK_SECRET!;

  // Calcular HMAC esperado
  const expected = 'sha256=' + createHmac('sha256', secret)
    .update(req.body)
    .digest('hex');

  // Comparação segura contra timing attacks
  const valid = timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );

  if (!valid) {
    return res.status(401).json({ error: 'Assinatura inválida' });
  }

  const { id, event, data } = JSON.parse(req.body.toString());
  
  // Processar evento
  handleEvent(id, event, data);
  
  res.status(200).json({ received: true });
});
Nunca ignore a validação HMAC em produção. Qualquer endpoint sem verificação pode ser chamado por agentes maliciosos, injetando eventos falsos no seu sistema.

Recebendo e Processando Eventos

app.post('/webhooks/engine', (req, res) => {
  // ... validação HMAC acima ...

  const { id, event, data } = JSON.parse(req.body.toString());
  
  // Idempotência: ignore eventos já processados
  if (await isAlreadyProcessed(id)) {
    return res.status(200).json({ received: true });
  }

  switch (event) {
    case 'invoice.authorized':
      await db.invoices.update({ 
        where: { id: data.id }, 
        data: { status: 'AUTHORIZED', accessKey: data.accessKey } 
      });
      await notifyCustomer(data.id);
      break;

    case 'invoice.rejected':
      await db.invoices.update({ 
        where: { id: data.id }, 
        data: { status: 'REJECTED' } 
      });
      await alertSupport(data);
      break;

    case 'certificate.expiring_soon':
      await sendEmail({
        to: 'admin@empresa.com',
        subject: '⚠️ Certificado digital vencendo em 30 dias',
        body: `Renove o certificado do CNPJ ${data.cnpj}`,
      });
      break;
  }

  await markAsProcessed(id);
  res.status(200).json({ received: true });
});

Política de Retentativas

Se seu endpoint retornar qualquer status diferente de 2xx ou não responder em 5 segundos, a Engine API vai tentar novamente:
TentativaAguardaTotal desde o evento
imediato0s
1 minuto1min
5 minutos6min
30 minutos36min
2 horas~2h36min
24 horas~26h36min
Após 6 falhas consecutivas, o evento é descartado e movido para a Dead Letter Queue, acessível via GET /admin/dead-letter-queue. Configure um alerta para monitorar essa fila.

Debugging Local

Para testar webhooks em desenvolvimento, use o webhook.site ou o ngrok:
# Com ngrok
ngrok http 3000

# Use a URL pública do ngrok ao cadastrar o webhook
# Ex: https://abc123.ngrok.io/webhooks/engine

Boas Práticas

Retorne 200 OK imediatamente e processe o evento em background (filas, workers). Nunca faça operações lentas dentro da requisição do webhook.
Use o campo id do evento como chave única. Se o mesmo evento chegar duas vezes (retentativa), não processe novamente.
Em produção, rejeite qualquer webhook sem assinatura válida. Use timingSafeEqual para evitar timing attacks.
Eventos que falharam 6 vezes ficam em /admin/dead-letter-queue. Processe-os manualmente quando seu sistema voltar.

Próximos passos

Erros e Rejeições

Entenda os códigos de erro da SEFAZ e como tratá-los

Sandbox

Teste seus webhooks sem emitir notas reais