Receive webhooks with a re-query
Some providers deliver webhooks you cannot verify in a composition. Banco Inter is one: it sends no signature on the body and expects the receiver to check its client certificate (mutual TLS). Orkestia's Hook endpoint does not check client certificates.
The safe pattern for such providers is hint + re-query. Treat each delivery only as a signal that something changed. Take the id from it, read that object from the provider's API over your own authenticated connection, and act only on the API's answer. A forged or replayed delivery then changes nothing: it either names an object the API does not know, or one already handled.
provider ──POST──▶ Hook URL ──▶ your composition
1. extract the id(s) from the body
2. re-read each id from the provider API (your credentials)
3. act only on the API answer, idempotent on the id
Use this pattern whenever a provider's webhook carries no signature you can check, or a signature you cannot hold in a composition.
1. Build the composition
The Hook starts your composition with the delivery as input. Declare every field the Hook sends, all optional. A composition refuses unknown input fields, so a missing declaration fails every delivery with Unknown field not in schema.
| Input | Type | Content |
|---|---|---|
payload | STRING | Raw request body as text |
raw_body_base64 | STRING | Raw body, base64 |
headers | JSON | Request headers |
metadata | JSON | method, path, query_params, content_type, event_uuid, redirect_uuid |
event_uuid | STRING | The Hook event (also the delivery reference) |
redirect_uuid | STRING | The Hook redirect that received it |
event_type | STRING | webhook |
action | STRING | Usually empty |
delivery_ref | STRING | Same as event_uuid |
source | STRING | hook |
Then the steps:
- Extract the ids with
control.text.extract_matchesoverpayload. For Inter Pix use the pattern"endToEndId"\s*:\s*"([^"]+)"; for Inter Cobrança use"codigoSolicitacao"\s*:\s*"([^"]+)". A delivery can carry several items, so expect a list. - Re-read each id from the provider with
control.http.request, using the same credential steps as your outbound sync. For Inter that means certificate and key fromvault.secret.resolve-many, an OAuth token, then:- Pix:
GET https://cdpj.partners.bancointer.com.br/pix/v2/pix/{endToEndId} - Cobrança:
GET https://cdpj.partners.bancointer.com.br/cobranca/v3/cobrancas/{codigoSolicitacao}
- Pix:
- Act only on the API answer: amount, status and payer come from the re-read, never from
payload. Key every write on the provider id (for example an AppData upsert keyed onendToEndId), so a repeated delivery is a no-op.
Never pass certificates, keys or tokens as literal values in the definition. Resolve them from the Vault at run time.
2. Create the Hook redirect
Run hook.create-redirect with the composition as the dispatch target. Use the composition's lineage ORN (from composition.save), so new versions are picked up without touching the redirect:
{
"name": "inter-pix",
"allowed_methods": ["POST"],
"configuration": {
"dispatch_workflow_type": "orn:orkestia:composition:<org>:lineage/<lineage_uuid>",
"dispatch_include_raw_body": true
}
}
The output's public_url is the path to register with the provider, on https://api.orkestia.dev. Its signed segment is the only secret in the URL. Treat it like a password: to rotate it, create a new redirect, register the new URL, then delete the old one with hook.delete-redirect.
The Hook answers 202 as soon as the delivery is stored, so a slow composition never makes the provider time out.
3. Register the URL with the provider
For Inter, register once per webhook kind with control.http.request, using the same certificate, key and token:
| Kind | Call |
|---|---|
| Pix received | PUT /pix/v2/webhook/{chave} with {"webhookUrl": "https://api.orkestia.dev/api/app/hook/input/<signed>"} |
| Boleto / BolePix (paid, cancelled, expired) | PUT /cobranca/v3/cobrancas/webhook with the same body |
| Payments you make | PUT /banking/v2/webhooks/{tipoWebhook} (pix-pagamento, boleto-pagamento) |
Test against Inter's sandbox first. Check one delivery arrives and starts your composition before switching to production.
4. Cover missed deliveries
A webhook is an accelerator, not the record. Inter retries a failed delivery four times: at 20, 30, 60 and 120 minutes for Pix and Cobrança, and at 5, 10, 30 and 60 minutes for Banking. After that it gives up.
- List failed sends with
GET /pix/v2/webhook/callbacks(Cobrança:GET /cobranca/v3/cobrancas/webhook/callbacks) and resend Pix ones withPOST /pix/v2/webhook/callbacks/retry. - Run a scheduled reconcile, for example daily, that lists the day's Pix and Cobrança movements from the API and applies anything the webhook missed. Because writes are keyed on the provider id, the reconcile and the webhook never double-count.
Hardening
The re-query is the security control here. If a provider or an auditor requires the client certificate itself to be checked at the edge, that needs a dedicated receiver that verifies the provider's CA. Ask us; it is not self-service.
Related
Guides
Task-oriented walkthroughs for building, governing, and operating Orkestia in your own cloud
Keep scheduled compositions cheap
Start compositions from data changes or cron times instead of minute polls, skip empty runs, keep pure logic inline, write only what changed, and read the cost estimate before you activate
