Skip to content

Webhooks -- Visão Geral

Webhooks permitem que sua aplicação receba notificações em tempo real sobre eventos na plataforma Owem Pay. Quando um evento ocorre, a Owem Pay envia um HTTP POST para a URL cadastrada.

Como Funciona

  1. Cadastre uma URL de webhook na sua conta
  2. Quando um evento ocorrer (ex: PIX recebido), a Owem Pay envia um HTTP POST para sua URL
  3. Sua aplicação processa a notificação e responde com status 2xx (200, 201 ou 204)

Eventos Disponíveis

A Owem Pay entrega apenas eventos relacionados ao PIX. Outros produtos (boleto, contas, STA, transferências não-PIX) não estão em escopo. Qualquer tentativa de assinar eventos fora da tabela abaixo é rejeitada com events: contains invalid events: ....

EventoStatus bodyDescriçãoDisparo
pix.charge.createdcreatedQR code gerado ou cash-in iniciadoAtivo
pix.charge.paidpaidPIX recebido e liquidadoAtivo
pix.charge.expiredexpiredQR code expirou sem pagamento (verificado a cada 5 min por worker)Ativo
pix.charge.cancelledcancelledQR code cancelado antes de pagamentoRegistrado, ainda não disparado
pix.payout.queuedqueuedPIX enviado enfileirado por rate limit (ClientLimiter por merchant ou bucket DICT BACEN). Retry automático a cada ~3s, com TTL máximo de 2hAtivo
pix.payout.processingprocessingPIX enviado, aguardando confirmação BACENAtivo
pix.payout.confirmedsettledPIX enviado e confirmado (terminal)Ativo
pix.payout.failedrejectedPIX enviado rejeitado pelo SPI (terminal)Ativo
pix.payout.returnedreturnedPIX enviado devolvidoAtivo
pix.refund.requestedrequestedPedido de devolução recebido (infração BACEN); bloqueio cautelar criado no saldo do clienteAtivo
pix.refund.completedsettled / completedAnálise da defesa finalizada e devolução executada (ou liberada)Ativo
pix.return.receivedsettledDevolução PIX recebida (crédito)Ativo
pix.infraction.createdACKNOWLEDGEDInfração PIX reportada pela contraparte via BACEN DICT; requer defesa ou gera bloqueio cautelar automático (>R$1k)Ativo
pix.infraction.resolvedCLOSED / CANCELLEDInfração resolvida (admin close, auto-deny ou contraparte cancelou)Ativo
pix.infraction.defense_submitteddefense_submittedDefesa submetida pelo merchant (portal ou API); aguarda análise BACENAtivo
webhook.testtestTeste manual. Disponível apenas via Admin/Merchant portal — a External API não expõe endpoint para disparar testeDisparo manual (não External API)

pix.charge.cancelled ainda não é disparado

O evento está no enum e pode ser assinado, mas o sistema não possui fluxo de cancelamento de QR code hoje. Se você assinar, o POST /webhooks responde 201 normalmente — porém nenhuma notificação chegará. Continue monitorando pix.charge.expired para o ciclo natural de vida do QR.

Segurança

Cada notificação inclui headers de segurança e identificação para validação:

HeaderDescrição
X-Owem-SignatureAssinatura HMAC-SHA256 do payload (prefixo sha256=). Em casos raros (webhook cadastrado sem secret), o valor literal é unsigned — veja nota abaixo
X-Owem-TimestampUnix timestamp em segundos do envio
X-Owem-Event-IdUUID único da delivery (para deduplicação)
X-Owem-Event-TypeTipo do evento (ex: pix.charge.paid)
Content-TypeSempre application/json
User-AgentSempre Owem-Webhook/1.0 — use para whitelisting em firewalls/WAF. Evolução futura seguirá o padrão Owem-Webhook/{version}; filtre por prefixo Owem-Webhook/ se quiser ficar imune a novas versões

Signature unsigned quando webhook não tem secret

Se o webhook foi cadastrado sem campo secret (cenário legado), o header X-Owem-Signature vale literalmente unsigned. Isso desabilita a validação HMAC do seu lado. Na prática, POST /api/external/webhooks gera um secret aleatório de 64 caracteres quando o campo é omitido (desde session 80), então o cenário só aparece em registros muito antigos ou via admin bypass. Se você receber unsigned, cadastre um novo webhook com secret explícito e remova o antigo.

SHA256 nos webhooks vs SHA512 na API

A API usa HMAC-SHA512 para autenticar requisições que você envia. Os webhooks enviados pela Owem Pay usam HMAC-SHA256 na assinatura X-Owem-Signature. São algoritmos diferentes -- cada um no seu contexto.

Validando a Assinatura

Valide a assinatura para garantir que a notificação foi enviada pela Owem Pay:

javascript
const crypto = require('crypto');

function validateWebhook(rawBody, timestamp, signature, secret) {
  // rawBody is the RAW request body string (before any JSON parse)
  // timestamp is unix seconds (e.g., 1712160000)
  const message = `${timestamp}.${rawBody}`;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Use o body RAW, não re-serializado

Você deve usar o corpo exato da requisição HTTP como os bytes chegaram na sua aplicação. Se fizer JSON.parse e depois JSON.stringify, os bytes resultantes não serão idênticos ao que a Owem usou para assinar, e a validação falhará.

Em Express/Node: use express.raw({ type: 'application/json' }) ou guarde o body antes de qualquer middleware de parse.

Em outras frameworks: configure para capturar o raw body antes do middleware JSON.

Ordenação de chaves em WEBHOOKS: NÃO é necessária

Para validação de webhooks (HMAC-SHA256) você NÃO precisa ordenar as chaves — use o body raw como recebido no HTTP request do Owem.

⚠️ Atenção — diferença vs envio de requisições: Na assinatura HMAC-SHA512 de REQUISIÇÕES que você envia, a ordenação alfabética das chaves É obrigatória (o servidor Owem reordena antes de validar). Não confunda os dois cenários:

  • Webhook recebido (HMAC-SHA256): valide o body raw sem reordenar
  • Request enviada (HMAC-SHA512): ordene suas chaves alfabeticamente antes de assinar

Valide sempre

Nunca processe um webhook sem validar a assinatura. Isso protege contra requisições falsificadas.

Adicionalmente, valide que o X-Owem-Timestamp está dentro de ± 5 minutos da hora atual (proteção anti-replay — o servidor não rejeita webhooks "antigos" por padrão; essa verificação cabe ao seu endpoint como defense-in-depth) e deduplique eventos por X-Owem-Event-Id (proteção contra retries).

Retry Policy

Se sua URL retornar um status diferente de 2xx (ou timeout após 30 s), a Owem Pay realiza até 8 tentativas com backoff exponencial. O total de tempo entre a primeira e a oitava tentativa é aproximadamente 7h45min:

TentativaDelay desde tentativa anteriorTempo acumulado
1a— (imediato, eager via Task.start)~50–200 ms
2a30 segundos~30 s
3a2 minutos~2,5 min
4a10 minutos~12,5 min
5a30 minutos~42,5 min
6a1 hora~1,75 h
7a2 horas~3,75 h
8a4 horas~7,75 h

Após 8 tentativas sem sucesso, o webhook_delivery é marcado com status failed e não é reenviado automaticamente. Você pode solicitar replay manual ao suporte Owem fornecendo o X-Owem-Event-Id (ou ter um operador com acesso admin replayar pelo portal).

Status de uma delivery

Cada delivery passa pelos status: pending (criada, aguardando entrega) → delivered (2xx recebido) OU failed (8 tentativas exauridas) OU expired (replay protection).

Sobre expired: quando o worker Oban vai processar a primeira tentativa e a delivery já tem mais de 5 minutos desde sua criação (inserted_at), o envio é abortado e o status vai direto para expired. Isso impede que reprocessamentos tardios (por acúmulo de fila, pod reiniciado, etc.) disparem notificações de eventos já velhos. Replays manuais solicitados ao suporte Owem passam pelo flag interno manual_replay: true e bypass esse guard — o cliente recebe a notificação normalmente.

Durabilidade

Antes de tentar a primeira entrega, o evento é persistido em webhook_deliveries no PostgreSQL. Se o pod que dispara o webhook cair durante a entrega, o Oban retoma automaticamente no próximo retry — nenhum evento é perdido.

Idempotência

Sua aplicação deve ser idempotente: se receber o mesmo evento mais de uma vez (identificado pelo X-Owem-Event-Id), deve processá-lo sem duplicar efeitos.

Replay manual via admin

Se uma delivery falhou e você precisa re-enviar, o time Owem pode executar replay manual via admin dashboard. Contate o suporte com o event_id da delivery.

Entregas duplicadas (race condition conhecida)

O sistema usa um mecanismo de "eager delivery" para acelerar a primeira entrega + um Oban worker como fallback durável de retry. Em cenários de alta concorrência, esses dois caminhos podem disparar o mesmo webhook em paralelo (janela de ~1 segundo). Nessa situação você recebe o mesmo payload 2 vezes via HTTP, mas com o mesmo X-Owem-Event-Id — é o mesmo event, não um retry.

Para evitar impacto duplicado:

  • Dedupe por X-Owem-Event-Id (recomendado — UUID único por delivery, estável em retries e na race condition acima)
  • Ou alternativamente dedupe por end_to_end_id + event_type quando fizer sentido para o evento

Isso é comportamento esperado, não erro. Retries legítimos (após 5xx/timeout) também reusam o mesmo X-Owem-Event-Id.

External ID nos Webhooks

Quando uma transação foi criada com external_id, esse campo é incluído no payload do webhook dentro do objeto data. Use-o para correlacionar o evento com o pedido no seu sistema sem precisar fazer uma consulta adicional.

Requisitos do Endpoint

  • A URL deve usar HTTPS (a menos que allow_insecure: true no cadastro)
  • Deve responder com status 2xx em até 30 segundos (receive_timeout configurado no worker de entrega)
  • O body da resposta é ignorado
  • Recomendado responder rápido (200 OK imediato) e processar o evento de forma assíncrona no seu lado; delays longos reduzem o throughput e aumentam chance de retries

Próximos Passos

Owem Pay Instituição de Pagamento — ISPB 37839059