Webhooks
Receba notificações em tempo real quando eventos fiscais acontecem. Configuração, segurança HMAC e boas práticas.
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
| Evento | Quando dispara |
|--------|---------------|
| invoice.authorized | Nota fiscal autorizada pela SEFAZ |
| invoice.rejected | Nota rejeitada pela SEFAZ |
| invoice.canceled | Nota cancelada |
| invoice.correction | Carta de correção emitida |
| mdfe.authorized | MDFe autorizado |
| mdfe.closed | MDFe encerrado |
| nfse.authorized | NFSe autorizada pela prefeitura |
| certificate.expiring_soon | Certificado vence em 30 dias |
| certificate.expiring_critical | Certificado vence em 7 dias |
| certificate.expired | Certificado 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:
sequenceDiagram
participant E as Engine API
participant S as Seu Servidor
E->>S: Tentativa 1 (imediato)
S-->>E: ❌ Falha / Timeout
Note over E: Aguarda 1 minuto
E->>S: Tentativa 2
S-->>E: ❌ Falha
Note over E: Aguarda 5 minutos
E->>S: Tentativa 3
S-->>E: ❌ Falha
Note over E: Aguarda 30 minutos
E->>S: Tentativa 4
S-->>E: ❌ Falha
Note over E: Aguarda 2 horas
E->>S: Tentativa 5
S-->>E: ✅ 200 OK
Note over E,S: Evento processado com sucesso
| Tentativa | Aguarda | Total desde o evento | |-----------|---------|---------------------| | 1ª | imediato | 0s | | 2ª | 1 minuto | 1min | | 3ª | 5 minutos | 6min | | 4ª | 30 minutos | 36min | | 5ª | 2 horas | ~2h36min | | 6ª | 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