Skip to content

Register Webhook

Endpoints to create, list, and remove notification webhooks.


Create Webhook

POST /api/external/webhooks

Headers

HeaderTypeRequiredDescription
AuthorizationStringYesApiKey {client_id}:{client_secret}
Content-TypeStringYesapplication/json
hmacStringYesHMAC-SHA512 signature of the body (learn more)

Request Body

FieldTypeRequiredDefaultDescription
urlStringYes--URL to receive notifications (HTTPS by default)
eventsArrayYes--List of events to subscribe to. Must be a non-empty array with at least one valid event from the table below. Omitting the field returns 400 {"errors": {"events": ["can't be blank"]}}.
secretStringNoauto-generatedKey for HMAC-SHA256 signing of deliveries. If omitted, a random value is auto-generated.
descriptionStringNonullFree description of the webhook for internal identification
allow_insecureBooleanNofalseAllow HTTP (non-HTTPS) URLs. Data security is the client's responsibility.

Available events (PIX only — other products are not in scope at Owem today):

EventStatus bodyDescriptionDispatch
pix.charge.createdcreatedQR code generated or cash-in initiatedActive
pix.charge.paidpaidPIX received and settledActive
pix.charge.expiredexpiredQR code expired without payment (worker every 5 min)Active
pix.charge.cancelledcancelledQR code cancelled before paymentRegistered, not yet dispatched
pix.payout.queuedqueuedPIX send queued by rate limit (per-merchant ClientLimiter OR BACEN DICT bucket)Active
pix.payout.processingprocessingPIX sent, awaiting confirmationActive
pix.payout.confirmedsettledPIX sent and confirmed (terminal)Active
pix.payout.failedrejectedPIX send rejected (terminal)Active
pix.payout.returnedreturnedSent PIX was returnedActive
pix.refund.requestedrequestedRefund request received (BACEN infraction); preventive block created on the client balanceActive
pix.refund.completedsettled / completedDefense analysis finalized and refund executed (or released)Active
pix.return.receivedsettledPIX return received (credit)Active
pix.infraction.createdACKNOWLEDGEDPIX infraction reported by counterparty via BACEN DICTActive
pix.infraction.resolvedCLOSED / CANCELLEDInfraction resolved (admin close, auto-deny or counterparty cancelled)Active
pix.infraction.defense_submitteddefense_submittedDefense submitted by the merchantActive
webhook.testtestManual test. Available only via Admin/Merchant portal — there is no External API endpoint to dispatchManual dispatch

pix.charge.cancelled is not yet dispatched

The event is in the valid subscriptions enum, but no production code dispatches this type of notification today (QR code cancellation flow not implemented). You may include it in the events array — the API accepts it — but no notification will arrive at your endpoint until the flow is implemented.

Any event outside this table is rejected

When creating a webhook, validate_events in the backend checks each item against the @valid_events enum (schema Fluxiq.Schemas.Webhooks.Webhook). Unknown events (boleto.paid, account.created, sta.file.*, etc.) return 400 with error events: contains invalid events: .... Before session 163 these names were in the enum as aspirational — they were removed because there were no dispatchers in code.

Payloads for each event

Full payload examples for each event are in Webhook Payloads.

Example

bash
BODY='{"url":"https://yoursite.com/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"

Success Response (201)

json
{
  "worked": true,
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "url": "https://yoursite.com/webhook",
  "events": ["pix.charge.paid", "pix.payout.confirmed"],
  "secret": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
  "description": null,
  "is_active": true,
  "created_at": "2026-03-07T15:30:00Z"
}

Format of id

The webhook id is a canonical UUID v4 (36 characters with hyphens). Use this value directly in DELETE /api/external/webhooks/:id.

Error Response (422)

json
{
  "worked": false,
  "detail": "URL deve utilizar HTTPS"
}

HTTPS only by default

The webhook URL must use HTTPS. URLs with HTTP will be rejected unless allow_insecure: true is sent on registration.

Important — Webhook Secret

The secret field returned in the registration response is the key used to sign webhook deliveries (HMAC-SHA256). Store this value securely as soon as you receive it — it is what validates that a notification actually came from Owem Pay.

Do NOT confuse with client_secret:

  • client_secret = authentication for your API requests (Authorization header)
  • Webhook secret = verification of received delivery signatures (X-Owem-Signature header)

If you do not send the secret field at registration, a random value will be auto-generated and returned in the response.

See Webhook Validation for examples of how to verify the signature.

Retrieving the secret later

The secret is returned both in POST /api/external/webhooks (creation) and in GET /api/external/webhooks and GET /api/external/webhooks/:id (query). If you lose the value, simply query the webhook again via GET.

In a future version this behavior may be restricted (display only on creation); we recommend storing the secret in a managed secret (vault, SSM, etc.) at registration time.

HTTP URLs

By default, webhooks require HTTPS to guarantee data security in transit. To use HTTP, send allow_insecure: true on registration.

Attention

HTTP URLs transmit data without encryption. The security and confidentiality of the information exchanged is the client's full responsibility. Owem Pay performs webhook delivery normally but assumes no liability for interception or data leakage on unencrypted connections.

Private URLs are always blocked

Even with allow_insecure: true, URLs pointing to private/internal addresses are rejected:

  • localhost / 127.x.x.x
  • RFC1918: 10.x.x.x, 192.168.x.x, 172.16-31.x.x
  • Internal TLDs: .local, .internal

Webhooks must point to public URLs accessible over the internet.


List Webhooks

GET /api/external/webhooks

Headers

HeaderTypeRequiredDescription
AuthorizationStringYesApiKey {client_id}:{client_secret}

Example

bash
curl -X GET https://api.owem.com.br/api/external/webhooks \
  -H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET"

Success Response (200)

Returns an array of objects (not wrapped in {"worked": true}). Each item contains the same fields as registration, including secret.

json
[
  {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "url": "https://yoursite.com/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"
  }
]
FieldTypeDescription
idstring (UUID)Webhook identifier
urlstringDestination URL
eventsarraySubscribed events
descriptionstring or nullOptional description
account_idinteger or nullAssociated account. null = webhook global for the API key (if supported)
is_activebooleanfalse = webhook paused, no delivery is dispatched
allow_insecurebooleantrue = HTTP URLs accepted
statusstringDerived from is_active"active" or "inactive"
secretstringHMAC-SHA256 key used to sign deliveries. Returned on LIST to allow recovery if the client lost the original value
created_at / updated_atstring ISO 8601Timestamps in UTC, format NaiveDateTime (without Z suffix, e.g., "2026-03-07T15:30:00"). Different from other date fields in webhook payloads (paid_at, returned_at, expired_at) which use DateTime ISO 8601 with trailing Z. Always assume UTC for the webhook object fields

Remove Webhook

DELETE /api/external/webhooks/:id

Headers

HeaderTypeRequiredDescription
AuthorizationStringYesApiKey {client_id}:{client_secret}

Path Parameters

ParameterTypeRequiredDescription
idString (UUID)YesWebhook ID (UUID v4 returned by the creation endpoint)

Example

bash
curl -X DELETE https://api.owem.com.br/api/external/webhooks/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET"

Success Response (204)

HTTP 204 No Content — empty body. The webhook was successfully removed; no pending delivery will be dispatched.

First call: 204. Subsequent: 404

The first successful call returns 204 No Content. Subsequent calls with the same id return 404 { "errors": { "not_found": "webhook not found" } } — the webhook was already removed. This is not strict HTTP idempotency (where every call would return 204) — it is the standard DELETE behavior when the resource ceases to exist. Write your integration to accept both 204 and 404 as "the webhook is no longer active".

Error Response (400)

Invalid id format (not UUID):

json
{
  "errors": {
    "bad_request": "id must be a valid UUID"
  }
}

Error Response (404)

Webhook does not exist or does not belong to your account:

json
{
  "errors": {
    "not_found": "webhook not found"
  }
}

Owem Pay Instituição de Pagamento — ISPB 37839059