Criar ou atualizar webhook subscription (idempotente)

Registra a URL do BFF do cliente que receberá os eventos do domain.
Operação idempotente: PUT no mesmo domain substitui a subscription.

Assinatura HMAC (X-Autra-Signature)

Cada POST enviado para a url carrega o header X-Autra-Signature:

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 (pix-payments, onboarding, transfers,
acquiring). 1 secret por tenant; não há secret separado por domínio.

Como obter o secret (1ª subscription do tenant):

  • Self-service (recomendado) — omita secret no body do PUT. O
    servidor gera 64 chars hex (256 bits) e devolve no campo secret
    da resposta. Aparece apenas nesta resposta (_secretShownOnce: true).
    GET/LIST nunca devolvem.
  • Forneça o seu — passe secret no body (mínimo 32 chars).
    A resposta ainda inclui o valor pra confirmação. Gere com
    openssl rand -hex 32.

Subscriptions seguintes do mesmo tenant: PUT em outros domínios
(ex.: já tem acquiring, agora cria pix-payments) reusam o secret
existente do tenant automaticamente — pode omitir o campo. Se passar
secret diferente, ROTACIONA pra todas as subscriptions do tenant.

Rotação: novo PUT com secret diferente em qualquer domínio →
atualiza o secret em TODAS as subscriptions do tenant (atomic).
Próximos retries usam o secret novo automaticamente.

Headers enviados em cada POST

HeaderValor
Content-Typeapplication/json
User-AgentAutra-Webhooks/1.0
X-Autra-Signaturesha256=<hex lowercase>
AuthorizationOpcional — conforme auth da subscription (Basic/Bearer)
(custom)Opcional — conforme auth.type=HEADER da subscription

Protocolo de validação no BFF do cliente

  1. Capturar o body raw da request — bytes exatos que chegaram na
    conexão, antes de qualquer parse/re-serialize do JSON.
  2. Ler o header X-Autra-Signature.
  3. Calcular expected = "sha256=" + hex(HMAC-SHA256(secret, raw_body)).
  4. Comparar com constant-time ao valor recebido. Igual → processa.
    Diferente → responder 401.

Node.js / Express (precisa do raw body — express.json({ verify: (req, _r, buf) => req.rawBody = buf })):

const crypto = require('crypto');
function verify(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), 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

  1. Body raw, NÃO o JSON re-serializado. JSON.parseJSON.stringify
    muda ordem das chaves / espaços e quebra a assinatura. Capture os bytes
    ANTES de qualquer parse.
  2. Comparação constant-time (timingSafeEqual / MessageDigest.isEqual
    / hmac.compare_digest / hmac.Equal). Não use == / .equals()
    vazam timing.
  3. Hex lowercase. Nossa implementação emite lowercase. Não aplique
    toUpperCase na comparação.
  4. 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" -hex

Se o hex resultante for igual ao que a Autra envia para o mesmo body +
secret, o BFF está pronto.

Domínios suportados

  • pix-payments — eventos terminais de Pix outbound (pix_payment.completed,
    pix_payment.failed, pix_payment.rejected, pix_payment.error).
    Eventos não-terminais (ACCEPTED, PROCESSING) não são enviados — use
    GET /v1/banking/accounts/{accountId}/pix-payments/{id} para polling.

  • onboarding — eventos de proposta de conta digital: terminais
    (onboarding.completed, onboarding.declined, onboarding.failed,
    onboarding.canceled) e onboarding.waiting_correction (a Autra
    rejeitou dados/arquivos e o portador precisa reenviar — acione o
    fluxo de correção no app). Demais estados intermediários
    (PROCESS_STARTED, FILES_PENDING, etc.) não disparam webhook —
    use GET /v1/banking/onboarding/{id} para acompanhar.
    Em onboarding.completed, operationData carrega dockAccountId,
    dockAgency e dockAccountNumber da conta recém-criada. Em
    onboarding.waiting_correction, operationData.analysis lista os
    arquivos rejeitados (invalid_files[]).

  • transfers — eventos de TED (Autra Pay). Status terminais separados
    por direção: transfer.in.completed / transfer.in.failed (TED
    recebida — crédito na conta do cliente) e transfer.out.completed /
    transfer.out.failed / transfer.out.canceled (TED enviada pela
    conta do cliente). Status intermediários da TED não disparam
    webhook. operationData carrega operationInstanceId, externalId,
    direction e counterparty (dados da contraparte).

  • acquiring — eventos de adquirência (PCH/Autra acquiring) entregues
    via SNS pela Autra. Pass-through: forwardamos o payload bruto da
    Autra dentro de operationData.payload. Cobre transações tanto de
    maquininha (CREDIT/DEBIT/PREPAID) quanto Pix Checkout (PIX). Event
    types: acquiring.financial_transaction, acquiring.payout,
    acquiring.financial_cycle, acquiring.payment_slip_notification,
    acquiring.merchant_onboarding_status. Roteamento do tenant é por
    slugMerchant (Autra) → tabela merchants; eventos sem merchant
    identificável (ORPHAN) não são enviados.

  • cards — eventos de cartões (banking). Event types:

    • Lifecycle (derivados de global_card): card.created,
      card.status_updated, card.pin_updated, card.updated,
      card.reissued.
    • Antifraude (global_fraud_prevention_actions_cards):
      card.fraud_action.
    • Transações (pass-through, payload bruto em
      operationData.payload): card.authorization (de
      global_authorization), card.charge (de global_charge_document).

    Roteamento de tenant: dock_card_id do payload → tabela autra.cards.
    Cartões fora do nosso DB viram ORPHAN. Estes eventos NUNCA carregam
    PAN ou CVV em claro
    — só masked_pan (ex.: 421847******4639).

  • ddaDébito Direto Autorizado. Eventos (Autra Notifications Central):

    • dda.bill_status_updated — derivado de global_dda_bill_payments.
      UM único event_type cobre todo o ciclo do boleto; o BFF discrimina
      pelo status_dda no payload (APRESENTADO | ACEITO | RECUSADO |
      AGENDADO | PAGO | BAIXADO). Pass-through: payload bruto Autra
      em operationData.payload (beneficiário, valor, vencimento,
      barcode, linha digitável, juros, multa, desconto, etc.).
    • dda.payer_status_updated — derivado de global_dda_payer.
      Mudança no enrollment do pagador (PENDING | PROCESSING |
      ACTIVE | DISABLED | DENIED). Disparado após enable/disable
      (que são assíncronos — REST responde PENDING e este webhook
      traz o estado final).

    Roteamento de tenant: account_id do payload Autra →
    autra.banking_accounts.provider_account_uuid. Endpoints REST de
    gerência (enable/disable/consult/list-bills) sob
    /v1/banking/accounts/{accountId}/dda (ver menu DDA).

Cada domain tem uma subscription independente — registre uma URL por
domínio (podem ser URLs diferentes).

Entrega

  • Síncrono inicial + retry automático — Tentativa síncrona ao
    gerar o evento (attempt 1). Se falhar (timeout, 5xx, conn refused),
    a entrega vai pra state = PENDING_RETRY com next_retry_at. O
    worker Autra_Webhook_Retry (EventBridge cron 1/min) reenvia até
    16 tentativas em ~60 min:
    • attempts 2–11 → +1 min cada (10 retries de 1 em 1 min);
    • attempts 12–16 → +10 min cada (5 retries de 10 em 10 min);
    • esgotado → state = DESPREZADO.
  • HMAC re-assinado a cada retry com o secret atual da subscription
    (rotação de secret reflete nos próximos retries).
  • Timeout 5s — BFF do cliente deve responder rápido (idealmente
    enfileirar internamente e retornar 200 imediato).
  • Idempotência — cada evento tem eventId único (UUID v7). Trate
    duplicatas no lado do BFF (mesmo eventId pode chegar até 16 vezes
    em caso de retry).
  • Validação do X-Autra-Signature — veja a seção "Assinatura HMAC"
    acima nesta página (snippets em Node/Java/Python/Go + pegadinhas).
Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…
Path Params
string
enum
required
Body Params

Payload de PUT /v1/banking/webhooks/{domain}.

Modelo per-tenant: cada tenant tem 1 (um) secret HMAC compartilhado
entre TODOS os domínios (pix-payments, onboarding, transfers,
acquiring). A 1ª subscription do tenant define o secret; subscriptions
seguintes do mesmo tenant reusam automaticamente.

O secret aparece UMA ÚNICA VEZ na resposta deste endpoint — guarde
imediatamente em vault. GET/LIST nunca devolvem o secret.

uri
required

URL HTTPS do BFF do cliente que receberá os POSTs (precisa começar com https://).

string
length ≥ 32

Opcional. Chave HMAC-SHA256 do tenant (compartilhada entre todos
os domínios). Comportamento:

  • 1ª subscription do tenant + secret omitido → servidor gera
    64 chars hex (256 bits) e devolve na resposta (recomendado).
  • 1ª subscription do tenant + secret informado → usa o valor
    passado (mínimo 32 chars).
  • Subscription seguinte do mesmo tenant + secret omitido
    reusa o secret existente do tenant (sem rotação).
  • Subscription seguinte do mesmo tenant + secret informado
    DIFERENTE do existente
    ROTAÇÃO: atualiza o secret em
    TODAS as subscriptions do tenant (pix-payments, onboarding,
    transfers, acquiring — todas que existirem). Retries em
    voo passam a usar o secret novo.

Sempre retornado na resposta deste PUT. Nunca retornado em GET/LIST.

eventTypes
array of strings
Defaults to *

Lista de tipos de evento a escutar. ["*"] (default) escuta todos
do domínio. Use os tipos do domínio correspondente — pix_payment.*
para pix-payments, onboarding.* para onboarding, transfer.*
para transfers, acquiring.* para acquiring, card.* para
cards.

eventTypes
boolean
Defaults to true

Quando false, a subscription permanece registrada mas eventos não são enviados (útil pra pausar temporariamente sem perder a config).

auth
object

Autenticação opcional que a Autra envia ao seu BFF em cada POST.
Independente da assinatura HMAC (X-Autra-Signature) — o HMAC
continua sempre presente e garante integridade. auth existe pra
satisfazer um gateway/proxy do receptor que exige Authorization
(Basic/Bearer) ou um header de API key.

Tipos:

  • NONE (default) — nada além do HMAC.
  • BASICAuthorization: Basic base64(username:password). Exige username + password.
  • BEARERAuthorization: Bearer <token>. Exige token.
  • HEADER — 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, token e value nunca são retornados em GET/LIST
(mesmo tratamento do secret). Pra trocar, faça outro PUT com o
novo valor — substitui.

Responses

400

INVALID_URL (precisa https://),
SECRET_TOO_SHORT (mínimo 32 chars, quando secret informado),
INVALID_EVENT_TYPE,
UNSUPPORTED_DOMAIN.

Language
Credentials
Bearer
JWT
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json