Registrar Webhook
Endpoints para crear, listar y remover webhooks de notificacion.
Crear Webhook
POST /api/external/webhooksHeaders
| Header | Tipo | Obligatorio | Descripcion |
|---|---|---|---|
Authorization | String | Si | ApiKey {client_id}:{client_secret} |
Content-Type | String | Si | application/json |
hmac | String | Si | Firma HMAC-SHA512 del body (mas informacion) |
Request Body
| Campo | Tipo | Obligatorio | Default | Descripcion |
|---|---|---|---|---|
url | String | Si | -- | URL para recibir notificaciones (HTTPS por defecto) |
events | Array | Si | -- | Lista de eventos a suscribir. Debe ser un array no-vacio con al menos un evento valido de la tabla abajo. Omitir el campo retorna 400 {"errors": {"events": ["can't be blank"]}}. |
secret | String | No | auto-generado | Clave para firma HMAC-SHA256 de las entregas. Si se omite, un valor aleatorio es generado automaticamente. |
description | String | No | null | Descripcion libre del webhook para identificacion interna |
allow_insecure | Boolean | No | false | Permite URLs HTTP (no-HTTPS). La seguridad de los datos es responsabilidad del cliente. |
Eventos disponibles (solo PIX — otros productos no estan en alcance en Owem hoy):
| Evento | Status body | Descripcion | Disparo |
|---|---|---|---|
pix.charge.created | created | QR code generado o cash-in iniciado | Activo |
pix.charge.paid | paid | PIX recibido y liquidado | Activo |
pix.charge.expired | expired | QR code expiro sin pago (worker cada 5 min) | Activo |
pix.charge.cancelled | cancelled | QR code cancelado antes del pago | Registrado, aun no disparado |
pix.payout.queued | queued | PIX enviado encolado por rate limit (ClientLimiter por merchant O bucket DICT BACEN) | Activo |
pix.payout.processing | processing | PIX enviado, aguardando confirmacion | Activo |
pix.payout.confirmed | settled | PIX enviado y confirmado (terminal) | Activo |
pix.payout.failed | rejected | PIX enviado rechazado (terminal) | Activo |
pix.payout.returned | returned | PIX enviado devuelto | Activo |
pix.refund.requested | requested | Solicitud de devolucion recibida (infraccion BACEN); bloqueo cautelar creado en el saldo del cliente | Activo |
pix.refund.completed | settled / completed | Analisis de la defensa finalizado y devolucion ejecutada (o liberada) | Activo |
pix.return.received | settled | Devolucion PIX recibida (credito) | Activo |
pix.infraction.created | ACKNOWLEDGED | Infraccion PIX reportada por la contraparte via BACEN DICT | Activo |
pix.infraction.resolved | CLOSED / CANCELLED | Infraccion resuelta (admin close, auto-deny o contraparte cancelo) | Activo |
pix.infraction.defense_submitted | defense_submitted | Defensa enviada por el merchant | Activo |
webhook.test | test | Prueba manual. Disponible solo via portal Admin/Merchant — no hay endpoint External API para disparar | Disparo manual |
pix.charge.cancelled aun no es disparado
El evento esta en el enum de suscripciones validas, pero ningun codigo en produccion dispara notificaciones de ese tipo hoy (flujo de cancelacion de QR code no implementado). Usted puede incluirlo en el array events — la API acepta — pero ninguna notificacion llegara a su endpoint hasta que el flujo sea implementado.
Cualquier evento fuera de esa tabla es rechazado
Al crear un webhook, validate_events en el backend verifica cada item contra el enum @valid_events (schema Fluxiq.Schemas.Webhooks.Webhook). Eventos desconocidos (boleto.paid, account.created, sta.file.*, etc.) retornan 400 con error events: contains invalid events: .... Antes de la sesion 163 esos nombres estaban en el enum como aspiracional — fueron removidos porque no habia dispatchers en codigo.
Payloads de cada evento
Ejemplos de payload completos para cada evento estan en Payloads de los Webhooks.
Ejemplo
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"Respuesta de Exito (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 del id
El id del webhook es un UUID v4 canonico (36 caracteres con guiones). Use ese valor directo en DELETE /api/external/webhooks/:id.
Respuesta de Error (422)
{
"worked": false,
"detail": "URL deve utilizar HTTPS"
}Solo HTTPS por Default
La URL del webhook debe usar HTTPS. URLs con HTTP seran rechazadas, a menos que allow_insecure: true sea enviado en el registro.
Importante — Secret del Webhook
El campo secret retornado en la respuesta del registro es la clave usada para firmar las entregas del webhook (HMAC-SHA256). Almacene ese valor con seguridad tan pronto lo reciba — es el que valida que una notificacion vino realmente de Owem Pay.
NO confunda con client_secret:
client_secret= autenticacion de sus solicitudes a la API (header Authorization)secretdel webhook = verificacion de la firma de las entregas recibidas (header X-Owem-Signature)
Si usted no envia el campo secret en el registro, un valor aleatorio sera generado automaticamente y retornado en la respuesta.
Vea Validacion de Webhooks para ejemplos de como verificar la firma.
Recuperando el secret despues
El secret es retornado tanto en POST /api/external/webhooks (creacion) como en GET /api/external/webhooks y GET /api/external/webhooks/:id (consulta). Si usted perdio el valor, basta consultar el webhook nuevamente via GET.
En una futura version este comportamiento puede ser restringido (exhibir solo en la creacion); recomendamos almacenar el secret en un secreto gestionado (vault, SSM, etc.) en el momento del registro.
URLs HTTP
Por defecto, los webhooks exigen HTTPS para garantizar la seguridad de los datos en transito. Para utilizar HTTP, envie allow_insecure: true en el registro del webhook.
Atencion
URLs HTTP transmiten datos sin cifrado. La seguridad y el secreto de las informaciones transitadas quedan bajo entera responsabilidad del cliente. Owem Pay realiza la entrega del webhook normalmente, pero no se responsabiliza por interceptacion o fuga de datos en conexiones no cifradas.
URLs privadas son siempre bloqueadas
Incluso con allow_insecure: true, URLs apuntando para direcciones privadas/internas son rechazadas:
localhost/127.x.x.x- RFC1918:
10.x.x.x,192.168.x.x,172.16-31.x.x - TLDs internos:
.local,.internal
Webhooks deben apuntar para URLs publicas accesibles por internet.
Listar Webhooks
GET /api/external/webhooksHeaders
| Header | Tipo | Obligatorio | Descripcion |
|---|---|---|---|
Authorization | String | Si | ApiKey {client_id}:{client_secret} |
Ejemplo
curl -X GET https://api.owem.com.br/api/external/webhooks \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET"Respuesta de Exito (200)
Retorna un array de objetos (no envuelto en {"worked": true}). Cada item contiene los mismos campos del registro, incluyendo 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 | Descripcion |
|---|---|---|
id | string (UUID) | Identificador del webhook |
url | string | URL de destino |
events | array | Eventos suscritos |
description | string o null | Descripcion opcional |
account_id | integer o null | Cuenta asociada. null = webhook global para la API key (si soportado) |
is_active | boolean | false = webhook pausado, ninguna delivery es disparada |
allow_insecure | boolean | true = URLs HTTP aceptadas |
status | string | Derivado de is_active — "active" o "inactive" |
secret | string | Clave HMAC-SHA256 usada para firmar entregas. Retornada en el LIST para permitir recuperacion caso el cliente haya perdido el valor original |
created_at / updated_at | string ISO 8601 | Timestamps en UTC, formato NaiveDateTime (sin sufijo Z, ej: "2026-03-07T15:30:00"). Diferente de otros campos de fecha en payloads de webhook (paid_at, returned_at, expired_at) que usan DateTime ISO 8601 con Z final. Siempre asuma UTC para los campos del webhook object |
Remover Webhook
DELETE /api/external/webhooks/:idHeaders
| Header | Tipo | Obligatorio | Descripcion |
|---|---|---|---|
Authorization | String | Si | ApiKey {client_id}:{client_secret} |
Path Parameters
| Parametro | Tipo | Obligatorio | Descripcion |
|---|---|---|---|
id | String (UUID) | Si | ID del webhook (UUID v4 retornado por el endpoint de creacion) |
Ejemplo
curl -X DELETE https://api.owem.com.br/api/external/webhooks/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET"Respuesta de Exito (204)
HTTP 204 No Content — body vacio. El webhook fue removido con exito; ninguna delivery pendiente sera disparada.
Primera llamada: 204. Subsecuentes: 404
La primera llamada exitosa retorna 204 No Content. Llamadas subsecuentes con el mismo id retornan 404 { "errors": { "not_found": "webhook not found" } } — el webhook ya fue removido. Esto no es idempotencia estricta HTTP (en que toda llamada retornaria 204) — es el comportamiento estandar de DELETE cuando el recurso deja de existir. Escriba su integracion para aceptar tanto 204 como 404 como "el webhook no esta mas activo".
Respuesta de Error (400)
Formato de id invalido (no es UUID):
{
"errors": {
"bad_request": "id must be a valid UUID"
}
}Respuesta de Error (404)
Webhook no existe o no pertenece a su cuenta:
{
"errors": {
"not_found": "webhook not found"
}
}