Skip to navigation

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. A customer who connected a bank and closed the tab has a session that completes on its own, a few minutes later, with what that bank returned; one who closed it before connecting anything has a session that waits until its time limit. So the browser leaving is not the end, and the redirect that takes your customer home is not a completion signal.

Polling for the real moment — the synchronous way — means asking a question whose answer is “not yet” almost every time. A webhook is the asynchronous way and it inverts that: you register an HTTPS URL, and INFONITE sends your backend an HTTP POST the moment something happens, server to server, long after the tab is closed if that is when it happens.

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.

Answer 2xx the moment the payload reaches you, whatever your own processing does with it afterwards: any other status is recorded as a failed delivery even though the event arrived. Answering a delivery has the handler to copy.


The three 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 three.

Which ones you actually need

EventWhy you would want it
endedThe one you need. It is the only event that says the session is over, and the only one delivered for a session that never got to connect anything.
consentThe audit moment: what your customer authorised, the version and hash of every text they accepted, and from where. Worth storing even if you do nothing with it.
startedA clock, not an instruction: it separates we sent them the link from they actually opened it.

The same event may reach you more than once. Deduplicate on session_id and event: an event you have already processed is one to acknowledge and drop. And if your endpoint was down when the session ended, do not wait for a retry — Get Session State is the authoritative answer.

ended carries messages — situations the session detected while it ran, beside the records themselves. The one worth routing is flag: it never means something failed on our side, it means the session found something about the person or the data that deserves a decision on yours — two banks that disagree about who your customer is, for one. When the banks do not agree.


What a delivery looks like

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

A delivery, as your server sees it
POST /webhooks/infonite?case=A-1029 HTTP/1.1
Host: api.yourcompany.com
Content-Type: application/json
Authorization: Bearer <the secret you gave us>
{
"event_class": "FinancialRiderV1FlowEvent",
"app_id": "644599da47847b79c03cc94a",
"customer_id": "cust_esp_202406",
"session_id": "644599da47847b79c03cc94f",
"producer": "financial-rider",
"event": "financial-rider:ended",
"date": "2026-09-12T10:17:52+00:00",
"status_code": "COMPLETED",
"status_message": "The end user finished",
"data": "aes::…the results, encrypted with your aes_key"
}

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

Fired once, when the session finishes — with the results of what it connected, or with the reason it has none.

event_class"FinancialRiderV1FlowEvent"Required

Payload class discriminator — always this value for Financial Rider events.

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

The event type: ended.

status_codeenumRequired
Describes the status of a flow.
is_closedtrueRequired

Always true: the session is over and its results are final — however it ended, and whether data travels or not. This field, not the widget closing, is what says a session is finished.

datestringOptionalformat: "datetime"
status_messagestring or nullOptional
dataobjectOptional

The session's final results.

  • Documented decrypted, delivered encrypted. With an aes_key on the session this field is the aes::Base64(IV + Ciphertext + Tag) string of the object described here, which decrypts into the session results; without one it is not in the body at all, and the results are read from the session results endpoint instead.
  • A session that ended without results — cancelled, timed out, failed — carries the reason instead, as 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.

The destination URL

The rules a destination has to satisfy — HTTPS, a public host, no query string of its own — are the same for every product and live in Receiving Webhooks. What is specific here is what the platform writes into the path: any of these is replaced with the event’s own value 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}financial-rider
{event}the full event name, e.g. financial-rider: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/644599da47847b79c03cc94f, 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
{
"customer_id": "cust_esp_202406",
"settings": {
"sandbox": false,
"hooks_extra_data": {
"case": "A-1029",
"stage": "underwriting",
"session": "{session_id}"
}
}
}

Your endpoint then receives ?case=A-1029&stage=underwriting&session=644599da47847b79c03cc94f. 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 holds for all three events: the context the session was opened from, the consent, and the results.

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: your customer’s financial 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

Endpoints and rules are per APPLICATION, and they are the same two pieces for every product on the platform: Receiving Webhooks is where they are explained, with the Console screens and the test delivery. A flow’s rules are bound to the flow as the producer, not to the banks it connects.