A plataforma Autra emite webhooks outbound pra notificar o BFF do cliente quando algo terminal acontece num evento (pagamento concluído, TED creditada, onboarding aprovado, transação acquiring autorizada etc.).
Esta página explica:
- Como o cliente cadastra a URL que vai receber os eventos.
- O formato exato do payload que enviamos.
- Como validar o
X-Autra-Signature(HMAC-SHA256). - Como tratar retries (até 16 tentativas em ~60 min).
Pra cadastrar/atualizar uma subscription, use PUT /v1/banking/webhooks/{domain}. Pra listar / consultar / remover, ver os outros endpoints em "Outbound Webhooks" na referência.
1. Como funciona
+-------------------+ +-------------------+ +------------------+
| Evento terminal | | Autra Service | | BFF do cliente |
| (Pix completo, | -----> | Dispatch (sync) | -----> | (URL cadastrada) |
| TED, onboarding, | | + HMAC + Auth | | |
| acquiring etc.) | +-------------------+ +------------------+
+-------------------+ |
| em caso de falha (timeout, 5xx, conn refused)
v
+------------------+
| Autra_Webhook_ |
| Retry (1/min, | -----> tenta de novo até 16x
| EventBridge) | em ~60 min
+------------------+
- Tentativa síncrona =
attempt 1(no momento do evento). - Retry automático =
attempts 2–16(worker que roda 1×/min). - Esgotado =
state = DESPREZADO, não tenta mais.
2. Domínios suportados
Cada domain tem uma subscription independente. Registre uma URL por domínio (podem ser URLs diferentes):
| Domain | Cobre |
|---|---|
pix-payments | Pix — pix_payment.completed, pix_payment.failed, pix_payment.rejected, pix_payment.error (Pix enviado) + pix_payment.received (Pix recebido / crédito entrante, status RECEIVED) |
onboarding | Conta digital — onboarding.completed, onboarding.declined, onboarding.failed, onboarding.canceled, onboarding.waiting_correction |
transfers | TED — transfer.in.completed, transfer.in.failed, transfer.out.completed, transfer.out.failed, transfer.out.canceled |
acquiring | Adquirência (POS + Pix Checkout) — acquiring.financial_transaction, acquiring.payout, acquiring.financial_cycle, acquiring.payment_slip_notification, acquiring.merchant_onboarding_status |
cards | Cartões (banking) — eventos do ciclo de vida + antifraude + transações. Ver detalhamento abaixo. |
dda | DDA (Débito Direto Autorizado) — dda.payer_status_updated (enrollment do pagador) + dda.bill_status_updated (ciclo do boleto). Ver detalhamento abaixo. |
pix-keys | Portabilidade de chave Pix (claim) — pix_keys.claim.requested, pix_keys.claim.confirmed, pix_keys.claim.completed, pix_keys.claim.denied, pix_keys.claim.canceled. Ver detalhamento abaixo. |
payments | Pagamento de conta/boleto/tributo a partir da conta (débito) — payment.completed (status PAID), payment.failed (DENIED/ROLLBACK/ERROR). |
boleto | Boleto emitido pela conta foi liquidado (crédito entrante) — boleto.paid (status PAID). |
Eventos intermediários (PROCESSING, ACCEPTED, FILES_PENDING etc.) não disparam webhook — use os endpoints de GET pra polling quando precisar do estado corrente.
Domain acquiring em detalhe
acquiring em detalheOs eventos de acquiring são roteados pelo merchant (slugMerchant do payload) → tenant dono. Só são entregues para merchants cadastrados/associados na plataforma Autra.
acquiring.merchant_onboarding_status: este evento sinaliza o andamento do cadastro de um EC (ex.:riskAnalysisStatus). Enquanto o merchant ainda não estiver cadastrado/associado na plataforma, o evento não é entregue (não há merchant resolvível para rotear ao tenant). Ele passa a ser entregue assim que o EC estiver vinculado ao seu tenant. Para acompanhar o cadastro antes disso, use o fluxo/endpoints de onboarding de merchant.
Domain cards em detalhe
cards em detalheApenas cartões cadastrados na plataforma Autra geram webhook. Cartões fora da plataforma são ignorados (não despacham).
| Event type | Quando dispara |
|---|---|
card.created | Cartão emitido |
card.status_updated | Mudança de status (ativo/bloqueado/cancelado) |
card.pin_updated | Troca/cadastro de PIN |
card.updated | Mudanças genéricas de atributos do cartão |
card.reissued | Cartão reemitido (2ª via) |
card.fraud_action | Bloqueio/ação por antifraude |
card.authorization | Autorização de compra (pass-through — payload da operação em operationData.payload) |
card.charge | Cobrança/estorno (pass-through) |
Segurança: estes eventos nunca carregam PAN ou CVV em claro. Só trafega masked_pan (ex.: 421847******4639).
Domain dda em detalhe
dda em detalheApenas contas cadastradas na plataforma Autra geram webhook. Contas fora da plataforma são ignoradas (não despacham).
| Event type | Quando dispara |
|---|---|
dda.payer_status_updated | Mudança de status do enrollment do pagador — PENDING → PROCESSING → ACTIVE, ou DISABLED / DENIED. O POST /dda retorna PENDING síncrono; a transição final pra ACTIVE chega por este webhook. |
dda.bill_status_updated | Qualquer mudança no ciclo de vida de um boleto DDA. Um único event_type cobre todo o ciclo — discrimine pelo status_dda no payload: APRESENTADO, ACEITO, RECUSADO, AGENDADO, PAGO/PAID, BAIXADO. |
Pré-requisito: o pagador precisa estar ACTIVE pra começar a receber dda.bill_status_updated. Antes disso só chega dda.payer_status_updated.
Pass-through: o payload completo da operação vai inteiro em operationData.payload — inclui o boleto completo (bar_code, digitable_line_number, bill_amount, original_beneficiary, interest, fine, calculated_amount etc.). Discrimine sempre pelo conteúdo do payload, não pelo eventType do envelope.
Domain pix-keys em detalhe
pix-keys em detalheCobre a portabilidade (claim) de chave Pix. Apenas contas/chaves da plataforma Autra geram webhook.
| Event type | Quando dispara |
|---|---|
pix_keys.claim.requested | Alguém pediu a portabilidade de uma chave que é sua (lado doador) — o cliente precisa autorizar ou contestar dentro do prazo |
pix_keys.claim.confirmed | Confirmação do doador recebida — chave em transferência |
pix_keys.claim.completed | Chave transferida com sucesso |
pix_keys.claim.denied | Portabilidade negada/contestada |
pix_keys.claim.canceled | Portabilidade cancelada pelo solicitante |
3. Formato do payload (envelope Autra)
Todos os domínios usam o mesmo envelope. Só muda eventType + o conteúdo de operationData.
Request
POST <sua-url-cadastrada>
Authorization: Basic <opcional, conforme auth da subscription>
Content-Type: application/json
User-Agent: Autra-Webhooks/1.0
X-Autra-Signature: sha256=<hex-lowercase>
{
"eventId": "01963e6f-aa00-7c00-9d00-000000000001",
"eventType": "acquiring.financial_transaction",
"occurredAt": "2026-05-20T15:25:00Z",
"tenantId": "2916bfe7-ecec-46bc-bc51-8a19aa593c1a",
"userDocument": "25055662000190",
"ownerId": "019d4f6e-c21d-4199-9424-9c37a2a1c65d",
"status": "AUTHORIZED",
"counterpartyName": "Fulano de Tal",
"operationData": {
"subject": "FINANCIAL_TRANSACTION",
"productType": "CREDIT",
"slugMerchant": "57DA076635814851A7A4B36FEDB340BB",
"orderId": "pedido-abc-123",
"pixId": "1a0b5369c4f24a0e8f0f0d7d0c2f9a11",
"payerName": "Fulano de Tal",
"payerDocument": "12345678901",
"payerEmail": "[email protected]",
"paymentLinkSlug": "lnk_n02754fh",
"payload": {
"rrn": "010008094933",
"muid": "26F9758DB70336D7547CCA2ECA1C76D4",
"slug": "4C037E901ADD45E29BA9C80C00A71745",
"totalAmount": 12.50,
"productType": "PREPAID_DEBIT",
"transactionStatus": "AUTHORIZED",
"...": "demais campos da operação — pass-through"
}
}
}
operationData.payloadtraz os dados da operação, repassados sem transformação. Parseie de forma tolerante — campos podem ser adicionados sem aviso. Em especial:
totalAmounté decimal em reais (ex.:12.50,0.01) — não é inteiro em centavos. Desserialize como número decimal/float, nunca comoint/long.productTypeé string — veja os valores conhecidos na tabela abaixo. Trate como lista aberta: não use enum fechado, novos valores podem surgir sem aviso.
Correlação e identificação do pagador (acquiring)
Além do payload cru, o operationData de acquiring.financial_transaction
e acquiring.payout traz campos de correlação quando aplicáveis (cada um
só aparece se houver valor — trate todos como opcionais):
| Campo | Quando vem | Pra que serve |
|---|---|---|
orderId | pedido criado com identificador próprio (API de checkout) | casar o evento com o pedido no seu sistema |
pixId | pagamentos Pix Checkout | mesmo valor devolvido no create (txid) |
payerName | o pagador preencheu os dados no checkout (Pix ou cartão) | exibir/conferir quem pagou |
payerDocument | idem — só dígitos, sem máscara (CPF ou CNPJ) | identificar o cliente quando o pagamento vem de um link público |
payerEmail | idem | alternativa de identificação quando seu cadastro é por e-mail |
paymentLinkSlug | pagamento originado de um link (/checkout/<slug>) | saber de qual link veio (ex.: um link por produto/finalidade) |
Link público, muitos clientes. Se você usa um único link paratodos os seus clientes, use
payerDocument(oupayerEmail) para saber a
quem creditar — é o dado que o próprio pagador preenche no checkout antes de
pagar. Não é necessário criar um link por cliente. Vale para link pago no
cartão e no Pix.
Correlação entre os dois eventos do mesmo pagamento:
acquiring.payout.payload.payoutId == acquiring.financial_transaction.payload.slug.
Valores de productType (acquiring)
productType (acquiring)Valores observados na base de produção da Autra (eventos acquiring.financial_transaction e acquiring.payout):
productType | Significado |
|---|---|
CREDIT | Cartão de crédito |
DEBIT | Cartão de débito |
PREPAID_CREDIT | Cartão pré-pago, função crédito |
PREPAID_DEBIT | Cartão pré-pago, função débito |
PIX | Transação Pix (QR / maquininha) |
VOUCHER | Voucher / benefício |
⚠️ Lista não fechada — reflete o que já trafegou; podem surgir valores novos. Desserialize productType como string e tenha um caminho de fallback para valores desconhecidos.
Campos top-level (sempre presentes)
userDocumenteownerIdsão preenchidos para o tráfego do app portador/merchant. Em alguns casos legado podem vir ausentes — nesse caso, roteie pelos dados emoperationData.
| Campo | Tipo | Descrição |
|---|---|---|
eventId | UUID | Identificador único do evento. Use pra idempotência (mesmo eventId pode chegar até 16 vezes em retry). |
eventType | string | Ex.: acquiring.financial_transaction, pix_payment.completed. |
occurredAt | ISO-8601 UTC | Momento em que o evento foi gerado pela Autra. |
tenantId | UUID | Tenant dono do evento (caso o BFF atenda vários tenants). |
userDocument | string | CPF (portador) ou CNPJ (merchant) do dono do evento — quem deve receber a notificação no app. tenantId sozinho não distingue qual usuário do tenant é o destinatário; roteie por (tenantId, userDocument). Resolvido pela conta/cartão/merchant. Pode vir ausente em casos legado. |
ownerId | UUID | ID interno estável do dono (não-PII): persons.id/legal_entities.id. Alternativa ao userDocument quando preferir não indexar por CPF/CNPJ. Mesma ressalva de ausência no legado. |
status | string | Status terminal (COMPLETED, AUTHORIZED, FAILED etc.). |
counterpartyName | string | Nome da contraparte (opcional): destinatário no envio (Pix/TED out), pagador no recebimento (Pix/TED in). Ausente quando não aplicável — use com fallback. |
flavor | string | Variante white-label do app do tenant (ex.: merci). Opcional — presente só quando o tenant usa um app white-label; ausente para o app padrão (autra). Use para roteamento/branding por marca. |
operationData | object | Conteúdo específico do evento. Em acquiring, traz payload com os dados da operação — parse tolerante; totalAmount decimal, productType string aberta (ver aviso acima) — e os campos de correlação/pagador (orderId, pixId, payerDocument, paymentLinkSlug…), todos opcionais. |
Headers enviados
| Header | Valor |
|---|---|
Content-Type | application/json |
User-Agent | Autra-Webhooks/1.0 |
X-Autra-Signature | sha256=<hex lowercase> — ver seção HMAC |
Authorization | Opcional, conforme auth da subscription (Basic ou Bearer) |
| (custom) | Opcional, conforme auth.type=HEADER da subscription |
4. Validação do X-Autra-Signature (HMAC-SHA256)
X-Autra-Signature (HMAC-SHA256)A cada POST mandamos:
X-Autra-Signature: sha256=<hex>
Onde <hex> é HMAC-SHA256(secret, raw_body) em lowercase hexadecimal. O secret é uma string de 32+ chars única por tenant — compartilhada entre todos os domínios. Não há secret separado por domínio.
O secret aparece apenas uma vez na resposta do PUT (_secretShownOnce: true). GET/LIST nunca o devolvem. Guarde em vault no momento do cadastro.
- 1ª subscription do tenant: sem secret no body → servidor gera. Com secret no body → usa o passado.
- Subscriptions seguintes do mesmo tenant: sem secret → reusa o secret existente (recomendado). Com secret diferente → ROTACIONA pra todas as subscriptions do tenant (atomic; retries em andamento passam a usar o novo).
Protocolo de validação no BFF
- Capturar o body raw da request — bytes exatos que chegaram na conexão, antes de qualquer parse/re-serialize do JSON.
- Ler o header
X-Autra-Signature. - Calcular
expected = "sha256=" + hex(HMAC-SHA256(secret, raw_body)). - Comparar com constant-time ao valor recebido. Igual → processa. Diferente → responder 401.
Snippets prontos
Node.js / Express — registre o verify no middleware pra ter o raw body:
const crypto = require('crypto');
app.use(express.json({
verify: (req, _res, buf) => { req.rawBody = buf; }
}));
function verifyAutraSignature(req, secret) {
const header = req.get('X-Autra-Signature') || '';
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.rawBody)
.digest('hex');
const a = Buffer.from(header);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Java / Spring — use ContentCachingRequestWrapper ou um Filter que cacheie o byte[] antes do JSON binding:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
public boolean verify(byte[] rawBody, String header, String secret) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] hash = mac.doFinal(rawBody);
StringBuilder hex = new StringBuilder("sha256=");
for (byte b : hash) hex.append(String.format("%02x", b));
return MessageDigest.isEqual(
hex.toString().getBytes(StandardCharsets.UTF_8),
header.getBytes(StandardCharsets.UTF_8)
);
}Python / FastAPI — use await request.body() antes de qualquer parse:
import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode("utf-8"),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header or "")Go (net/http):
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func verify(rawBody []byte, header, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(rawBody)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(header))
}Pegadinhas comuns
- Body raw, NÃO o JSON re-serializado.
JSON.parse→JSON.stringifymuda a ordem das chaves / espaços e quebra a assinatura. Capture os bytes ANTES de qualquer parse. - Comparação constant-time (
timingSafeEqual/MessageDigest.isEqual/hmac.compare_digest/hmac.Equal). Não use==/.equals()— vazam timing. - Hex lowercase. Nossa implementação emite lowercase. Não aplique
toUpperCasena comparação. - Prefixo
sha256=faz parte do header. Compare a string inteira ("sha256=" + hex) ou strippe de ambos os lados.
Teste de mesa (validar o cálculo localmente)
echo -n '{"test":"hello"}' | openssl dgst -sha256 -hmac "SEU_SECRET" -hexSe o hex resultante for igual ao que a Autra envia para o mesmo body + secret, o BFF está pronto.
Como gerar um secret forte (caso queira fornecer o seu, em vez de deixar o servidor gerar)
openssl rand -hex 32 # 64 chars hex (256 bits)
openssl rand -base64 48 | tr -d '\n' # ~64 chars base645. Auth opcional no transporte (Basic / Bearer / Header custom)
Além do HMAC (que é integridade do payload), a subscription suporta autenticação de transporte opcional pra satisfazer um gateway/proxy do cliente que exige Authorization ou um header de API key. Tipos:
auth.type | Comportamento |
|---|---|
NONE (default) | Nada além do HMAC. |
BASIC | Envia Authorization: Basic base64(username:password). Exige username + password. |
BEARER | Envia Authorization: Bearer <token>. Exige token. |
HEADER | Envia header custom (ex.: x-api-key: <value>). Exige name + value. Não pode usar nomes reservados (Host, Content-Length, Content-Type, X-Autra-Signature, User-Agent). |
password,tokenevalueaparecem apenas na resposta do PUT (mesmo tratamento dosecret). GET/LIST devolvem sóauth.type(eauth.name, setype=HEADER— o nome do header em si não é segredo). Pra trocar, faça outro PUT com o novo valor.
6. Política de entrega + retry
| Fase | O que acontece | Coluna state |
|---|---|---|
| Síncrono (attempt 1) | Tentativa imediata no momento do evento. Timeout 5s. | SUCCEEDED se 2xx; senão PENDING_RETRY |
| Retry rápido (attempts 2–11) | Worker reenvia +1 min cada → 10 tentativas em 10 min. | PENDING_RETRY |
| Retry lento (attempts 12–16) | Worker reenvia +10 min cada → 5 tentativas em 50 min. | PENDING_RETRY |
| Esgotado | Após attempt 16 falho, não tenta mais. | DESPREZADO |
Total: 16 tentativas em ~60 min antes de DESPREZAR. Cada retry re-assina o HMAC com o secret atual do tenant (rotação propaga pra todas as subscriptions do tenant; retries em voo passam a usar o novo).
O que o BFF deve fazer
- 2xx → entrega marcada como sucesso. Não retenta.
- Qualquer outro status / timeout / erro de rede → entra na fila de retry.
- Timeout: 5s. BFF do cliente deve responder rápido. Recomendação: enfileirar internamente (ex.: SQS, Kafka, fila in-memory) e retornar 200 imediato; processar de forma assíncrona.
- Idempotência: o mesmo
eventIdpode chegar até 16 vezes. UseeventIdcomo chave de dedup no BFF.
7. Como cadastrar a subscription
O secret é opcional — se omitido, o servidor gera 64 chars hex (256 bits). Recomendado pra self-service: você não precisa cuidar da geração nem do canal de entrega.
1 secret por tenant — compartilhado entre todos os domínios. Se você já criou uma subscription emacquiringe agora vai criar empix-payments(mesmo tenant), basta omitirsecretque o servidor reusa o existente. Você não precisa rastrear secrets diferentes por domínio.
Variante A — server gera o secret (recomendado)
curl -X PUT https://api.autra.io/v1/banking/webhooks/acquiring \
-H "Authorization: Bearer <JWT-do-tenant>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://bff-do-cliente.com.br/webhooks/autra",
"eventTypes": ["*"],
"enabled": true,
"auth": {
"type": "BASIC",
"username": "autra-webhook",
"password": "<senha-do-bff>"
}
}'Variante B — você fornece o secret
curl -X PUT https://api.autra.io/v1/banking/webhooks/acquiring \
-H "Authorization: Bearer <JWT-do-tenant>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://bff-do-cliente.com.br/webhooks/autra",
"secret": "<32+ chars; gere com openssl rand -hex 32>",
"eventTypes": ["*"],
"enabled": true,
"auth": { "type": "BASIC", "username": "autra-webhook", "password": "..." }
}'Resposta do PUT (contém secret revelado — guarde agora!)
secret revelado — guarde agora!){
"id": "9f7a...",
"tenantId": "2916bfe7-ecec-46bc-bc51-8a19aa593c1a",
"domain": "acquiring",
"url": "https://bff-do-cliente.com.br/webhooks/autra",
"secret": "EXAMPLE_3f019b734d51e1ed4298ac49fea42a26bef159724247d83bddf0e561",
"eventTypes": ["*"],
"enabled": true,
"auth": {
"type": "BASIC",
"username": "autra-webhook",
"password": "<senha-cru>"
},
"createdAt": "2026-05-20T15:25:00Z",
"updatedAt": "2026-05-20T15:25:00Z",
"_secretShownOnce": true
}
Esta é a única resposta ondesecret,password,tokenevalueaparecem. Salve agora em vault —GET /v1/banking/webhookseGET /v1/banking/webhooks/{domain}sempre redactam (devolvem sóauth.typeeauth.namese HEADER).
Ver detalhes completos em PUT /v1/banking/webhooks/{domain} na referência da API.
8. Suporte
Em caso de dúvida sobre validação HMAC, retries não chegando, ou cadastro de domínios, fale com o time Autra. Em pedidos de troubleshooting, anexe eventId e timestamp aproximado — conseguimos localizar a delivery e ver attempt, state, response_status e error registrados.
