Cadastrar Webhook
Endpoints para criar, listar e remover webhooks de notificação.
Criar Webhook
POST /api/external/webhooksHeaders
| Header | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Authorization | String | Sim | ApiKey {client_id}:{client_secret} |
Content-Type | String | Sim | application/json |
hmac | String | Sim | Assinatura HMAC-SHA512 do body (saiba mais) |
Request Body
| Campo | Tipo | Obrigatório | Padrão | Descrição |
|---|---|---|---|---|
url | String | Sim | -- | URL para receber notificações (HTTPS por padrão) |
events | Array | Sim | -- | Lista de eventos para assinar. Deve ser um array não-vazio com pelo menos um evento válido da tabela abaixo. Omitir o campo retorna 400 {"errors": {"events": ["can't be blank"]}}. |
secret | String | Não | auto-gerado | Chave para assinatura HMAC-SHA256 das entregas. Se omitido, um valor aleatório é gerado automaticamente. |
description | String | Não | null | Descrição livre do webhook para identificação interna |
allow_insecure | Boolean | Não | false | Permite URLs HTTP (não-HTTPS). A segurança dos dados é responsabilidade do cliente. |
Eventos disponíveis (apenas PIX — outros produtos não estão em escopo na Owem hoje):
| Evento | Status body | Descrição | Disparo |
|---|---|---|---|
pix.charge.created | created | QR code gerado ou cash-in iniciado | Ativo |
pix.charge.paid | paid | PIX recebido e liquidado | Ativo |
pix.charge.expired | expired | QR code expirou sem pagamento (worker a cada 5 min) | Ativo |
pix.charge.cancelled | cancelled | QR code cancelado antes de pagamento | Registrado, ainda não disparado |
pix.payout.queued | queued | PIX enviado enfileirado por rate limit (ClientLimiter por merchant OU bucket DICT BACEN) | Ativo |
pix.payout.processing | processing | PIX enviado, aguardando confirmação | Ativo |
pix.payout.confirmed | settled | PIX enviado e confirmado (terminal) | Ativo |
pix.payout.failed | rejected | PIX enviado rejeitado (terminal) | Ativo |
pix.payout.returned | returned | PIX enviado devolvido | Ativo |
pix.refund.requested | requested | Pedido de devolução recebido (infração BACEN); bloqueio cautelar criado no saldo do cliente | Ativo |
pix.refund.completed | settled / completed | Análise da defesa finalizada e devolução executada (ou liberada) | Ativo |
pix.return.received | settled | Devolução PIX recebida (crédito) | Ativo |
pix.infraction.created | ACKNOWLEDGED | Infração PIX reportada pela contraparte via BACEN DICT | Ativo |
pix.infraction.resolved | CLOSED / CANCELLED | Infração resolvida (admin close, auto-deny ou contraparte cancelou) | Ativo |
pix.infraction.defense_submitted | defense_submitted | Defesa submetida pelo merchant | Ativo |
webhook.test | test | Teste manual. Disponível apenas via portal Admin/Merchant — não há endpoint External API para disparar | Disparo manual |
pix.charge.cancelled ainda não é disparado
O evento está no enum de assinaturas válidas, mas nenhum código em produção dispara notificações desse tipo hoje (fluxo de cancelamento de QR code não implementado). Você pode incluí-lo no array events — a API aceita — porém nenhuma notificação chegará ao seu endpoint até que o fluxo seja implementado.
Qualquer evento fora dessa tabela é rejeitado
Ao criar um webhook, validate_events no backend verifica cada item contra o enum @valid_events (schema Fluxiq.Schemas.Webhooks.Webhook). Eventos desconhecidos (boleto.paid, account.created, sta.file.*, etc.) retornam 400 com erro events: contains invalid events: .... Antes da sessão 163 esses nomes estavam no enum como aspiracional — foram removidos porque não havia dispatchers em código.
Payloads de cada evento
Exemplos de payload completos para cada evento estão em Payloads dos Webhooks.
Exemplo
BODY='{"url":"https://seusite.com.br/webhook","events":["pix.charge.paid","pix.payout.confirmed"]}'
HMAC=$(echo -n "$BODY" | openssl dgst -sha512 -hmac "$CLIENT_SECRET" | awk '{print $2}')
curl -X POST https://api.owem.com.br/api/external/webhooks \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-H "hmac: $HMAC" \
-d "$BODY"Resposta de Sucesso (201)
{
"worked": true,
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"url": "https://seusite.com.br/webhook",
"events": ["pix.charge.paid", "pix.payout.confirmed"],
"secret": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"description": null,
"is_active": true,
"created_at": "2026-03-07T15:30:00Z"
}Formato do id
O id do webhook é um UUID v4 canônico (36 caracteres com hífens). Use esse valor direto em DELETE /api/external/webhooks/:id.
Resposta de Erro (422)
{
"worked": false,
"detail": "URL deve utilizar HTTPS"
}Apenas HTTPS por Padrão
A URL do webhook deve utilizar HTTPS. URLs com HTTP serão rejeitadas, a menos que allow_insecure: true seja enviado no cadastro.
Importante — Secret do Webhook
O campo secret retornado na resposta de cadastro é a chave usada para assinar as entregas do webhook (HMAC-SHA256). Armazene esse valor com segurança assim que receber — é ele quem valida que uma notificação veio realmente da Owem Pay.
NÃO confunda com client_secret:
client_secret= autenticação das suas requisições à API (header Authorization)secretdo webhook = verificação da assinatura das entregas recebidas (header X-Owem-Signature)
Se você não enviar o campo secret no cadastro, um valor aleatório será gerado automaticamente e retornado na resposta.
Veja Validação de Webhooks para exemplos de como verificar a assinatura.
Recuperando o secret depois
O secret é retornado tanto em POST /api/external/webhooks (criação) quanto em GET /api/external/webhooks e GET /api/external/webhooks/:id (consulta). Se você perdeu o valor, basta consultar o webhook novamente via GET.
Em uma futura versão este comportamento pode ser restringido (exibir apenas na criação); recomendamos armazenar o secret em um segredo gerenciado (vault, SSM, etc.) no momento do cadastro.
URLs HTTP
Por padrão, webhooks exigem HTTPS para garantir a segurança dos dados em trânsito. Para utilizar HTTP, envie allow_insecure: true no cadastro do webhook.
Atenção
URLs HTTP transmitem dados sem criptografia. A segurança e o sigilo das informações trafegadas ficam sob inteira responsabilidade do cliente. A Owem Pay realiza a entrega do webhook normalmente, porém não se responsabiliza por interceptação ou vazamento de dados em conexões não criptografadas.
URLs privadas são sempre bloqueadas
Mesmo com allow_insecure: true, URLs apontando para endereços privados/internos são rejeitadas:
localhost/127.x.x.x- RFC1918:
10.x.x.x,192.168.x.x,172.16-31.x.x - TLDs internos:
.local,.internal
Webhooks devem apontar para URLs públicas acessíveis pela internet.
Listar Webhooks
GET /api/external/webhooksHeaders
| Header | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Authorization | String | Sim | ApiKey {client_id}:{client_secret} |
Exemplo
curl -X GET https://api.owem.com.br/api/external/webhooks \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET"Resposta de Sucesso (200)
Retorna um array de objetos (não envelopado em {"worked": true}). Cada item contém os mesmos campos do cadastro, incluindo secret.
[
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"url": "https://seusite.com.br/webhook",
"events": ["pix.charge.paid", "pix.payout.confirmed"],
"description": null,
"account_id": 10014,
"is_active": true,
"allow_insecure": false,
"status": "active",
"secret": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"created_at": "2026-03-07T15:30:00",
"updated_at": "2026-03-07T15:30:00"
}
]| Campo | Tipo | Descrição |
|---|---|---|
id | string (UUID) | Identificador do webhook |
url | string | URL de destino |
events | array | Eventos assinados |
description | string ou null | Descrição opcional |
account_id | integer ou null | Conta associada. null = webhook global para a API key (se suportado) |
is_active | boolean | false = webhook pausado, nenhuma delivery é disparada |
allow_insecure | boolean | true = URLs HTTP aceitas |
status | string | Derivado de is_active — "active" ou "inactive" |
secret | string | Chave HMAC-SHA256 usada para assinar entregas. Retornada no LIST para permitir recuperação caso o cliente tenha perdido o valor original |
created_at / updated_at | string ISO 8601 | Timestamps em UTC, formato NaiveDateTime (sem sufixo Z, ex: "2026-03-07T15:30:00"). Diferente de outros campos de data em payloads de webhook (paid_at, returned_at, expired_at) que usam DateTime ISO 8601 com Z final. Sempre assuma UTC para os campos do webhook object |
Remover Webhook
DELETE /api/external/webhooks/:idHeaders
| Header | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Authorization | String | Sim | ApiKey {client_id}:{client_secret} |
Path Parameters
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | String (UUID) | Sim | ID do webhook (UUID v4 retornado pelo endpoint de criação) |
Exemplo
curl -X DELETE https://api.owem.com.br/api/external/webhooks/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET"Resposta de Sucesso (204)
HTTP 204 No Content — body vazio. O webhook foi removido com sucesso; nenhuma delivery pendente será disparada.
Primeira chamada: 204. Subsequentes: 404
A primeira chamada bem-sucedida retorna 204 No Content. Chamadas subsequentes com o mesmo id retornam 404 { "errors": { "not_found": "webhook not found" } } — o webhook já foi removido. Isso não é idempotência estrita HTTP (em que toda chamada retornaria 204) — é o comportamento padrão de DELETE quando o recurso deixa de existir. Escreva sua integração para aceitar tanto 204 quanto 404 como "o webhook não está mais ativo".
Resposta de Erro (400)
Formato de id inválido (não é UUID):
{
"errors": {
"bad_request": "id must be a valid UUID"
}
}Resposta de Erro (404)
Webhook não existe ou não pertence à sua conta:
{
"errors": {
"not_found": "webhook not found"
}
}