Webhook Público SAP→Lefia
Como configurar SAP (ou outro sistema externo) para postar documentos no Lefia via webhook autenticado.
URL e Secret
Em /mapeamentos/integracao/[id], o card "Webhook público" mostra:
- URL pública:
https://lefia.ai/api/mapeamentos/webhook/<path>(copy button) - Secret: mascarado. Para revelar/copiar, clique em Regenerar — o novo secret é exibido uma única vez (após isso só será possível regenerar de novo, invalidando o anterior).
Por segurança, o server nunca retorna o secret atual. Para descobrir o secret você precisa regenerar.
Configurar SAP
Opção A — Output Management (S/4HANA moderno)
Configurar condition record com REST destination apontando pra URL + header customizado:Content-Type: application/jsonx-lefia-timestamp: <segundos Unix>x-lefia-signature-v2: sha256=<HMAC-SHA256(secret, "<timestamp>.<corpo>")>
Opção B — ABAP custom (ECC ou S/4)
BAdI emVBAK_VBAP_SAVE (ou similar) que dispara cl_http_client=>create_by_url com POST.Exemplo ABAP completo é exibido no card colapsável "Exemplos de uso".
Opção C — cURL (teste manual)
SECRET='whsec_...'
BODY='{"docnum":"0005000123","mestyp":"SALES_ORDER","format":"json","data":{"SalesOrder":"0005000123"}}'
TS=$(date +%s)
SIG="sha256=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')"
curl -X POST "https://lefia.ai/api/mapeamentos/webhook/<path>" \
-H "Content-Type: application/json" \
-H "x-lefia-timestamp: $TS" \
-H "x-lefia-signature-v2: $SIG" \
-H "idempotency-key: doc-12345" \
-d "$BODY"
Assinatura (v2, contra reenvio)
Cada chamada é assinada com o secret, que nunca trafega:
x-lefia-timestamp: a hora do envio em segundos Unix;x-lefia-signature-v2:sha256=+ HMAC-SHA256 do texto<timestamp>.<corpo>com o secret.
O carimbo entra na assinatura e só vale por 5 minutos: um corpo capturado não pode ser reenviado depois. Calcule a assinatura na hora de cada envio.
Integrações que já existiam aceitam o formato antigo (
x-webhook-secret no cabeçalho) até a data mostrada no cartão do webhook. Até lá a resposta traz Deprecation: true e Sunset: <data>; depois, só a v2. Integração nova já nasce exigindo a v2.Body esperado
{
"docnum": "0005000123",
"mestyp": "SALES_ORDER",
"format": "json",
"direction": "inbound",
"data": { "SalesOrder": "0005000123", "SoldToParty": "100001" },
"sourceStatus": "received"
}
Idempotência
- Header
idempotency-key(opcional, recomendado): se reenviar com mesmo valor, retorna a mensagem original sem reprocessar. - Se não enviar, Lefia computa fingerprint do payload e dedupliça automaticamente.
Respostas
- 200: deduplicado (mensagem já recebida)
- 202 Accepted: processado, retorna
messageId+pipeline_status - 401: assinatura inválida, ausente, fora da janela de 5 min, ou formato antigo depois do prazo (a mensagem diz o que mandar)
- 503 (com
Retry-After): o secret guardado no Lefia não pôde ser lido — reenvie depois; o dono vê o aviso no cartão e gera um secret novo - 404: path não encontrado
- 423 Locked: integração não está ativa
Health check
GET /api/mapeamentos/webhook/<path> retorna 200 + {ok, status, expects} se o path existe. Útil pro time do cliente validar antes de configurar POST com payload real.Tipo de mensagem e recusas
A mensagem recebida usa o tipo de mensagem ativo da integração no ambiente dela. Com mais de um tipo ativo, o Lefia reconhece o tipo pelo conteúdo: o cabeçalho do EDIFACT (
UNH) e do X12 (ST/GS), o registro de controle do IDoc (EDI_DC40), a versão e o modelo da NF-e, o resourceType do FHIR, o contexto do OData (@odata.context na V4, __metadata na V2) — comparando com o que o catálogo declara para cada tipo. No JSON livre, o campo que identifica a mensagem é declarado no perfil do formato. O mestyp/format do corpo só desempata entre tipos que o conteúdo não separa.- 422
message_type_ambiguous: nada identifica um tipo só (ou o conteúdo casa igualmente mais de um). Nada é gravado. Reenviar igual não adianta: informe o tipo no corpo, deixe um só tipo ativo ou peça ao administrador para declarar a identidade da mensagem. O administrador da empresa recebe um aviso com o link da integração. - 503
message_type_lookup_failed: falha passageira ao ler a configuração. Nada é gravado; reenvie em instantes.
Arquivo (SFTP/FTP): o arquivo não identificado fica na origem e a recusa é registrada; nos ciclos seguintes ele não é baixado de novo enquanto a configuração de tipos não mudar. Mudou (um tipo desativado, identidade declarada), ele é tentado outra vez.
REST e Oracle: a execução lê registros, não um documento que se identifique. Com mais de um tipo ativo ela para com o motivo no log e o aviso de configuração; deixe um só tipo ativo.
Arquivo EDI do parceiro (EDIFACT, X12, IDoc)
Quando a integração tem mapeamento e a mensagem de origem é EDIFACT, X12 ou IDoc, o Lefia lê o arquivo na estrutura da mensagem antes de aplicar o mapeamento: segmentos, grupos (por exemplo, cada
LIN com os seus QTY e MOA), repetições e posições vêm do catálogo da mensagem, e os separadores vêm do próprio arquivo (UNA/ISA). O envelope (UNB, UNH, ISA, GS, ST…) fica à parte, em _envelope. Um arquivo com várias mensagens vira uma mensagem por documento, cada uma com o trecho exato do arquivo nos dados originais. A 1ª é processada na hora; as outras entram na fila de processamento, que passa a cada 5 minutos e leva até 50 mensagens por vez — um arquivo com 500 documentos termina em cerca de 50 minutos. Arquivo fora da forma da spec fica em erro com a linha e o segmento. Sem mapeamento, o arquivo segue como chegou.Encadear uma saída do Lefia nesta entrada
Para testar saída e entrada de ponta a ponta sem um destino externo (por exemplo no Lefia Lab), a saída de uma integração pode entregar no webhook de entrada de outra:
- Na integração de entrada (gatilho webhook, ativa), clique Regenerar no card do webhook e copie o secret (aparece uma vez).
- Use a URL
https://lefia.ai/api/webhook/<id da integração de entrada>— ela aceita o arquivo cru (EDIFACT, X12, XML, JSON). A URL/api/mapeamentos/webhook/<caminho>espera o envelope JSON descrito acima. - Na integração de saída, conexão de destino Webhook: essa URL, Assinar o corpo = Lefia v2 e o secret copiado em Segredo da assinatura.
A entrada verifica a assinatura v2 como a de qualquer remetente; o
Idempotency-Key da saída faz o reenvio de uma mesma mensagem não duplicar.Histórico de versões
- v1.1.0
Proteção contra reenvio na entrada assinada (/api/edi-hub/webhook/<caminho>, /api/webhook/<id>, /api/event/<id>): assinatura v2 = x-lefia-timestamp + x-lefia-signature-v2 (HMAC-SHA256 de "<timestamp>.<corpo>", o mesmo esquema do envio em webhooks/dispatch), recusada fora de 5 minutos — o secret nunca trafega. Exigência por integração (integrations.inbound_signature_v2_from): nova nasce exigindo; as que existiam ganharam 30 dias, com o contrato antigo (x-webhook-secret no cabeçalho ou HMAC só do corpo) aceito com Deprecation/Sunset na resposta e aviso no log. A primeira requisição v2 válida de uma integração no prazo fecha o contrato antigo (inbound_signature_v2_from passa a ser agora). Reenvio do mesmo corpo dentro da janela devolve a mensagem original nas três entradas (bloco comum ingest/message-dedup: idempotency-key, fingerprint sempre gravado, índices únicos e 23505 — o /api/event deixou o upsert por docnum que rerodava o pipeline). Cartão do webhook mostra a exigência e o prazo, com exemplos cURL e ABAP assinando em v2; ajuda e protocolo atualizados.
25/09/2026 · updated
- 1.0.1
Promovido staging → main no merge release 49edb06
13/05/2026 · updated
- 1.0.0
Endpoint público POST /api/edi-hub/webhook/[path] + GET health + regenerate-secret API + UI WebhookCard (URL pública, secret regenerável, cURL/ABAP examples). UNIQUE index em webhook_path. Constant-time compare via crypto.timingSafeEqual. Reusa fingerprint+idempotency+processMessage do receive-message.
13/05/2026 · new