Webhooks

Let INFONITE tell your backend what happened, instead of asking it over and over
View as Markdown

A session does not finish when your customer closes the widget. It finishes when the last public administration answers — which can be seconds later, or minutes later when the CIRBE report from the Banco de España is involved. Polling for that moment means asking a question whose answer is “not yet” almost every time.

A webhook inverts it: you register an HTTPS URL, and INFONITE sends your backend an HTTP POST the moment something happens. You do nothing until there is something to do.

Webhooks are the recommended integration. Everything they deliver is also available by polling Get Session State and Get Session Results — keep that path as your fallback, not as your design.


The four events

Each event is delivered independently, and you subscribe to as many or as few as you need. Every one carries the same envelope, so a single handler can serve all four.

partial is an early view, never a conclusion. It exists so you can move before the slow sources answer, and the records in it may still change, be corrected, or appear for the first time. Branch on is_closed, exactly as described in knowing when a session is finished.


What a delivery looks like

A single POST, with a JSON body, to the URL you registered.

A delivery, as your server sees it
1POST /webhooks/infonite?case=A-1029 HTTP/1.1
2Host: api.yourcompany.com
3Content-Type: application/json
4Authorization: Bearer <the secret you gave us>
5
6{
7 "event_class": "EsPublicAdministrationV1FlowEvent",
8 "app_id": "644599da47847b79c03cc94f",
9 "customer_id": "cust_esp_202406",
10 "session_id": "644599da47847b79c03cc950",
11 "producer": "es-public-administration",
12 "event": "es-public-administration:ended",
13 "date": "2026-08-28T09:14:02.881Z",
14 "status_code": "COMPLETED",
15 "status_message": null,
16 "data": { "": "the results, or aes::… when encrypted" }
17}

The envelope is identical across the four events — only event, status_code and data differ:

Fired when the session finishes — successfully with the complete results, or on cancellation/failure with the reason.

event_class"EsPublicAdministrationV1FlowEvent"Required

Payload class discriminator — always this value for Spain Public Administration events.

app_idstringRequiredformat: "object-id"
customer_idstringRequired1-64 characters
session_idstringRequiredformat: "object-id"
producer"es-public-administration"Required
The flow that produced the event.
event"es-public-administration:ended"Required

The event type: ended.

status_codeenumRequired
Describes the status of a flow.
datestringOptionalformat: "datetime"
status_messagestring or nullOptional
dataobjectOptional

Verification results payload.

  • On success (COMPLETED):
    • If an aes_key was provided during session creation, the results are delivered encrypted. Please refer to the Payload Decryption section of the API introduction to decrypt them into FlowResultsSchema.
    • If no aes_key was specified, this field is not informed (null), and you must retrieve the results manually using the session API.
  • On cancellation or failure, it contains error/feedback details conforming to WebhookEndedErrorPayload.

customer_id is yours. It is the identifier you supplied at Create Session, so a handler can route the event to the right case in your system without a lookup table.

Answer quickly, and answer 2xx

RuleWhy
Any 2xx means “received”The body of your response is not read. Do not put a decision in it.
The call times out after 5 secondsAcknowledge first, process afterwards. Queue the payload and return — do not score, decide or write to a slow system inside the request.
Treat every handler as idempotentThe same event may reach you more than once. Key your side effects on session_id + event.
Do not design around a retryIf your endpoint was down when the event fired, the reliable recovery is to poll — is_closed in Get Session Results is the authoritative signal, whatever you missed.

Authenticating the call

Your endpoint is on the public internet, so it needs to know that a delivery really came from us. Each destination can carry one static authentication header, sent on every delivery to it:

1

Pick the header

Either Authorization or a custom x-… header of your own — x-webhook-token, x-infonite-signature, whatever your gateway already understands. Headers the delivery itself owns (Host, Content-Type) cannot be overridden.

2

Mint the secret

You choose the value, up to 1024 characters: a bearer token, a shared secret, an API-gateway key. It is stored encrypted.

3

Verify it on arrival

Reject anything that does not carry it, with a constant-time comparison. A 401 from you is a delivery failure on our side — which is what you want for a forged call.

The secret is write-only. You can set it and you can replace it; nothing in the platform — no screen, no endpoint, no support agent — will read it back to you. Store your own copy when you mint it.

Never put credentials in the URL. A destination like https://user:pass@api.yourcompany.com/hook is refused: it would be stored in clear and shown on every read, which is the opposite of what the encrypted header next to it is for. The same goes for a token in a query parameter — see below.

Where the calls come from

Deliveries leave through our API gateway, from a fixed set of addresses. If your endpoint sits behind a firewall that only accepts known sources, you can allow-list them.

The current list — production and pre-production — is shown in the Console, beside your application’s webhook settings, and support will provide it on request. It is not published here on purpose: an address list printed in a document goes stale the day we scale or fail over, and a stale copy in your firewall is an outage you would discover as missing deliveries.

An allow-list is a filter, not an authentication. It proves a request came from INFONITE’s infrastructure — not that it came from your application, and not that the body was not tampered with. Keep the authentication header; the allow-list narrows who can reach your endpoint, the header proves who is calling.


The destination URL

1

HTTPS, to a public host

Plain http, private addresses (10.x, 192.168.x, 172.16-31.x), loopback and link-local ranges, and internal-only names are all refused. A stored destination is fired from inside our platform on a schedule, so it has to prove it points at the open internet.

2

No query string of its own

Register https://api.yourcompany.com/webhooks/infonite, not …/infonite?source=x. The query string belongs to the delivery, which fills it in at send time — a stored one could only collide with it.

3

Template variables, if you want them in the path

Any of these are replaced with the event’s own values before the call is made:

VariableBecomes
{session_id}the session this event belongs to
{customer_id}the identifier you supplied at session creation
{app_id}your application
{producer}es-public-administration
{event}the full event name, e.g. es-public-administration:ended
{status_code}the session status at the time of the event

So https://api.yourcompany.com/webhooks/infonite/{session_id} arrives as …/webhooks/infonite/644599da47847b79c03cc950, and your router can pick the session out of the path.

Sending your own context along

hooks_extra_data is a dictionary you set per session, in settings at Create Session. Every key and value in it is appended to the delivery as query parameters, and the same template variables work inside them:

At session creation
1{
2 "customer_id": "cust_esp_202406",
3 "settings": {
4 "hooks_extra_data": {
5 "case": "A-1029",
6 "stage": "underwriting",
7 "session": "{session_id}"
8 }
9 }
10}

Your endpoint then receives ?case=A-1029&stage=underwriting&session=644599da47847b79c03cc950. It is the natural place for the correlation id of whatever process opened the session.

hooks_extra_data is not a place for secrets. Query strings end up in access logs, proxies and browser-adjacent tooling. Authentication belongs in the header.


Encrypted payloads — and what travels without a key

We never send personal data over a webhook unless you gave us a key to encrypt it with.

The aes_key you supply at Create Session is not an option that improves the payload — it is the condition for there being a payload at all.

That gives you two delivery modes, and choosing between them is an architectural decision, not a preference:

At session creationWhat the event carriesHow you get the results
With aes_keyThe full data, as aes::Base64(IV + Ciphertext + Tag)Decrypt it in your handler. Nothing else to call.
Without aes_keyNo data at all — the field is absentThe event is a notification: it tells you that something happened, and you fetch the records yourself over the authenticated API with Get Session Results.

The rest of the envelope — session_id, customer_id, event, status_code, date — is always in clear, in both modes. It carries no personal information, so you can route, log and deduplicate an event without decrypting or fetching anything.

The keyless mode is a perfectly good design, and for some architectures it is the better one: the sensitive records never touch your webhook endpoint at all, they are pulled over an authenticated call from the service that is going to process them. What is not a design is expecting data to be there without a key — it will not be.


Configuring your webhooks

Webhook configuration is made of two pieces, which is what lets several events share one destination and lets you rotate a domain in one place:

PieceWhat it is
An endpointA destination: a name, the HTTPS base URL, and optionally the authentication header. Stated once, reused by every event that delivers to it.
A ruleA binding: this event goes to that endpoint. One rule per event, each with its own on/off switch — pausing partial while you rework a handler never touches ended.

Self-service is coming to the console — very soon. 🚀 The screens to create endpoints, bind events to them and rotate the authentication header are being built right now.

In the meantime, contact support with the destination URL for each event you want to receive and, if you want one, the header name and value to send. We will configure it for your application and confirm when it is live. If you have no rules configured yet, no events are delivered — polling still works, and the widget still runs normally.

The hooks/list, hooks/update and hooks/delete endpoints are deprecated and no longer configure anything. They remain in the API so existing tooling does not break, but a change made through them has no effect on delivery. Use the process above until the console screens ship.