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"
}'
await fetch ( 'https://api.engineapi.com.br/webhooks' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
url: 'https://seusite.com/webhooks/engine' ,
events: [ 'invoice.authorized' , 'invoice.rejected' , 'certificate.expiring_soon' ],
secret: 'meu-segredo-hmac' ,
}),
});
httpx.post(
'https://api.engineapi.com.br/webhooks' ,
headers = { 'Authorization' : f 'Bearer { token } ' },
json = {
'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.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 });
});
import hashlib
import hmac
import os
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.post ( '/webhooks/engine' )
async def handle_webhook ( request : Request):
body = await request.body()
signature = request.headers.get( 'x-engineapi-signature' , '' )
secret = os.environ[ 'WEBHOOK_SECRET' ].encode()
# Calcular HMAC esperado
expected = 'sha256=' + hmac.new(secret, body, hashlib.sha256).hexdigest()
# Comparação segura
if not hmac.compare_digest(signature, expected):
raise HTTPException( status_code = 401 , detail = 'Assinatura inválida' )
import json
payload = json.loads(body)
event_id = payload[ 'id' ]
event = payload[ 'event' ]
data = payload[ 'data' ]
# Processar evento
await process_event(event_id, event, data)
return { '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
Node.js (Express)
Python (FastAPI)
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 });
});
@app.post ( '/webhooks/engine' )
async def handle_webhook ( request : Request):
# ... validação HMAC acima ...
payload = json.loads(body)
event_id = payload[ 'id' ]
event = payload[ 'event' ]
data = payload[ 'data' ]
# Idempotência
if await is_already_processed(event_id):
return { 'received' : True }
if event == 'invoice.authorized' :
await db.invoices.update(data[ 'id' ], status = 'AUTHORIZED' )
await notify_customer(data[ 'id' ])
elif event == 'invoice.rejected' :
await db.invoices.update(data[ 'id' ], status = 'REJECTED' )
await alert_support(data)
elif event == 'certificate.expiring_soon' :
await send_email(
to = 'admin@empresa.com' ,
subject = 'Certificado vencendo em 30 dias' ,
)
await mark_as_processed(event_id)
return { '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:
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
Boas Práticas
Responda rápido, processe depois
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.
Valide a assinatura HMAC sempre
Em produção, rejeite qualquer webhook sem assinatura válida. Use timingSafeEqual para evitar timing attacks.
Monitore a Dead Letter Queue
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