Webhooks
Recebe notificações automáticas quando um documento chega a um estado final — evita ter de fazer polling constante.
A URL do webhook é configurada no dashboard. A activação, porém, depende do teu endpoint responder correctamente ao desafio abaixo — é isso que esta página cobre, junto com o formato dos eventos reais.
Um webhook por conta, não por empresa
Se a tua conta gere várias empresas, todas partilham a mesma URL de webhook — não é preciso (nem possível) configurar uma diferente por empresa.
Isso significa que o teu endpoint vai receber eventos de todas as empresas da conta misturados no mesmo lugar. Todo evento traz tenant_id no payload (topo, junto com event) — usa esse campo para saberes de qual empresa veio e encaminhares/filtrares no teu lado (ex.: por tenant_id correspondente a cada empresa registada no teu ERP).
Se geres várias empresas, valida sempre tenant_id antes de processar o evento — nunca assumas que o payload pertence a uma empresa só porque é a única que configuraste até agora.
Activação (challenge-echo)
Depois de configurares a URL no dashboard, o webhook começa inactivo — eventos reais só são entregues depois de activares. Ao pedir a activação (no dashboard), o Gateway envia um pedido de teste ao teu endpoint:
POST <a tua URL>
Content-Type: application/json{ "event": "webhook.verify", "challenge": "3f8a1c9b2d4e5f6a" }O teu endpoint tem de responder, em até 5 segundos, com HTTP 200 e um corpo JSON que ecoe exactamente o mesmo challenge:
{ "challenge": "3f8a1c9b2d4e5f6a" }Se o valor coincidir, o webhook passa a active e começa a receber eventos reais. Caso contrário (status diferente de 200, corpo sem o challenge correcto, ou timeout), a activação falha e tens de tentar novamente depois de corrigir o endpoint.
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/webhooks/agt")
async def receber_webhook(request: Request):
body = await request.json()
if body.get("event") == "webhook.verify":
return {"challenge": body["challenge"]}
# ... tratar eventos reais (ver abaixo)
return {"ok": True}Eventos reais
POST <a tua URL>
Content-Type: application/json
X-Webhook-Signature: sha256=<hmac hex>{
"event": "request.completed",
"service_type": "registar_factura",
"tenant_id": "b3f1c2a4-1234-4a5b-9c6d-7e8f9a0b1c2d",
"timestamp": "2026-07-27T10:00:00Z",
"data": {
"request_id": "uuid",
"agt_request_id": "202500000000118",
"status": "VALID",
"response": { "gateway_status": "VALID", "agt_status_code": 1, "agt_data_response": {} },
"error_list": []
}
}data.status segue os mesmos valores finais descritos em Consultar Estado.
Séries: series.created / series.failed
Pedidos de criação de série (POST /serie ou POST /serie/all) disparam um evento dedicado quando a AGT responde, em vez do genérico request.completed:
{
"event": "series.created",
"service_type": "solicitar_serie",
"tenant_id": "b3f1c2a4-1234-4a5b-9c6d-7e8f9a0b1c2d",
"timestamp": "2026-07-27T10:00:00Z",
"data": {
"request_id": "uuid",
"agt_request_id": "202500000000118",
"status": "SUCCESS",
"response": { "gateway_status": "SUCCESS", "agt_status_code": 1, "agt_data_response": { "seriesCode": "FT SEDE 2026", "authorizedQuantity": 1000 } },
"error_list": []
}
}Se a AGT rejeitar o pedido, o evento é series.failed (mesma estrutura, status e error_list reflectem o motivo). Usa POST /serie/all para pedir todas as séries em falta de uma vez — a resposta HTTP é imediata (QUEUED para cada tipo), o resultado real chega por este webhook.
Validar a assinatura
O segredo do teu webhook está disponível no dashboard. Cada entrega é assinada com HMAC-SHA256 sobre o corpo bruto do pedido.
Valida sempre a assinatura antes de confiar no payload.
import hmac, hashlib
def verificar_assinatura(corpo_bruto: bytes, header_assinatura: str, secret: str) -> bool:
esperado = "sha256=" + hmac.new(secret.encode(), corpo_bruto, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperado, header_assinatura or "")Boas práticas
- Responde
200de imediato e processa o payload de forma assíncrona no teu lado — o Gateway espera até 10s pela resposta. - Não dependas só do webhook: usa
GET /v1/client/status/{requestId}como rede de segurança para reconciliar estados, caso alguma entrega falhe. - Se o teu endpoint falhar 5 vezes consecutivas, o webhook é desactivado automaticamente — reactiva-o no dashboard depois de corrigir o problema. Como o webhook é por conta, isto pára a entrega de eventos de todas as empresas da conta, não só da que gerou a falha.
- Com mais de uma empresa, usa sempre
tenant_idpara rotear o evento internamente — nunca dependas da URL ou de qualquer outro campo para identificar a origem.