Voltar para Central de ajuda
FT-WEBHOOK-PUBLIC· v1.1.0Integrações

Webhook Público SAP→Lefia

Como configurar SAP (ou outro sistema externo) para postar documentos no Lefia via webhook autenticado.

Atualizado em 26/09/2026
Quando uma integração tem paradigma Push, o Lefia expõe um endpoint público que aceita POST de sistema externo.

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/json
  • x-lefia-timestamp: <segundos Unix>
  • x-lefia-signature-v2: sha256=<HMAC-SHA256(secret, "<timestamp>.<corpo>")>


Opção B — ABAP custom (ECC ou S/4)

BAdI em VBAK_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:

  1. Na integração de entrada (gatilho webhook, ativa), clique Regenerar no card do webhook e copie o secret (aparece uma vez).
  2. 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.
  3. 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