> This page is for Financial Rider Flow.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://infonite.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://infonite.dev/_mcp/server.

# Authentication & keys

Three different things are protected in a Financial Rider integration, and each has its own key. **Your backend** proves who it is with your application secret. **Your customer's browser** talks to the widget with a token of the session's own. And **what our servers push to yours** can be encrypted with a key only you hold.

| Key                                 | Who holds it                    | What it protects                                                                                                |                            |
| :---------------------------------- | :------------------------------ | :-------------------------------------------------------------------------------------------------------------- | :------------------------- |
| Application secret (`X-APP-SECRET`) | your backend                    | every call to this API: creating a session, reading it, cancelling it, deleting it, downloading its attachments | required                   |
| The session's token                 | the widget, inside `widget_url` | your customer's journey through that one session                                                                | issued with the session    |
| `aes_key`                           | you, per session                | the `data` of every [webhook](/flows/financial-rider/webhooks) the session sends                                | optional, recommended      |
| `token_id` + `token_key`            | you, per connection             | recurring access: reading a bank again with nobody present                                                      | only with recurring access |

---

## The application secret

Every call carries it:

```http
X-APP-SECRET: <your application secret>
```

> **Warning**
>
> **The secret belongs on your server and nowhere else.** It can act for any of your customers and read every record extracted under your application. It must never reach a browser, a mobile binary, a front-end bundle or anything a user can open — there is no client-side use of these APIs, by design.

Secrets are issued per application in the [console](https://www.infonite.tech/console), and an application can hold more than one. That is what makes rotation uneventful: mint the new secret, deploy it, then revoke the old one. Revoking a secret never touches the application, its sessions, its executions or its data. [Security & keys](/console/security-and-keys) is the screen.

**When authentication fails**, the answer is an HTTP status and a stable `detail` code — branch on the code, show your own message:

| Status | `detail`                     | What happened                                     |
| :----- | :--------------------------- | :------------------------------------------------ |
| `401`  | `not_authenticated`          | The header was missing.                           |
| `401`  | `invalid_application_secret` | The value is not a well-formed secret.            |
| `401`  | `unauthorized`               | It is well formed but unknown.                    |
| `401`  | `app_secret_revoked`         | It was valid and has been revoked.                |
| `403`  | `app_pending`                | The application exists but is not approved yet.   |
| `403`  | `app_locked`                 | The application has been locked. Contact support. |
| `403`  | `app_archived`               | The application has been archived.                |

**Sandbox or production is decided per session**, with `settings.sandbox`, and a production session needs your application in production and holding its capability — [Sandbox and production](/flows/financial-rider/overview#sandbox-and-production) has what that takes. The base URLs, the error shape and the headers every response carries are the same for every API: [Using the API](/guides/using-the-api).

---

## The widget signs in on its own

The `widget_url` that Create Session returns carries a **token of its own, valid for that one session**. It is what lets your customer's browser talk to us while they go through the widget — it is not your secret, and nothing in it reaches your application's other sessions or the API you call from your backend. Treat it as you would the session itself: it goes to the customer it was created for, and to nobody else.

---

## The session key: `aes_key`

**A key you mint, once per session, so that what our servers push to yours is readable only by you.** It is optional and recommended: 32 random bytes, Base64-encoded, sent once in `settings.aes_key` when you create the session.

|                  | With an `aes_key`                                                                                                                 | Without one                                                        |
| :--------------- | :-------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------- |
| **The webhooks** | Every event the session sends carries its `data`, encrypted under your key as `aes::Base64(IV + ciphertext + tag)` — AES-256-GCM. | They still tell you what happened, and carry **no `data` at all**. |
| **The results**  | Read with your secret, as always.                                                                                                 | Read with your secret — the only way to get them.                  |

> **Note**
>
> **The key encrypts the webhooks, and only the webhooks.** The results endpoint and the attachment downloads answer to your application secret, over TLS; they are never encrypted with the session key. That is why a webhook without a key carries nothing: the one channel that reaches a public endpoint of yours is the one that must not carry records in the clear.

What follows from it:

* **Mint a fresh key for every session.** One key per session keeps one session's payloads from opening another's.
* **Keep it until the session's last webhook.** You need it to decrypt each one, the final event included. Store it server-side, beside the `session_id`.
* **We never give it back.** It is not in Get Session Settings, nor in any other answer — losing it means reading that session's results from the results endpoint instead.

**`Minting a key`**

```bash title="Minting a key"
openssl rand -base64 32
```

**`Python`**

```python title="Python"
import base64, os
aes_key = base64.b64encode(os.urandom(32)).decode()
```

**`Node.js`**

```javascript title="Node.js"
const aesKey = require('node:crypto').randomBytes(32).toString('base64');
```

The decryption — strip `aes::`, split IV, ciphertext and tag, open with AES-256-GCM — is on the [Payload Encryption](/guides/payload-encryption) guide, with an interactive sandbox to test it and working code in Node.js, Python and Java.

---

## Nothing to encrypt towards us

Your customer types their credentials into the widget, not into your systems, so **a flow's calls carry nothing for you to encrypt**. The application's inbound key pair — the one [Direct Executions](/direct-executions/authentication#encrypting-the-credentials-you-send) uses so that the credentials you send are unreadable on the way — plays no part in integrating a flow.

Without a key, [Get Session Results](https://infonite.dev/flows/financial-rider/api-reference/financial-rider-api/sessions-results/flows-financial-rider-v-1-session-results) is how the results reach you; with one, the final webhook brings them too. [Payload Decryption](/flows/financial-rider/overview#payload-decryption-aes-256-gcm), on the overview, has the envelope of this flow's events.

---

## Recurring access: the token key

With [recurring access](/flows/financial-rider/recurring-access), each bank your customer connects is exchanged for a token, on the Direct Executions API and with your application secret: [Exchange a ticket](https://infonite.dev/direct-executions/api-reference/direct-executions-api/tokenization/direct-executions-v-1-token-exchange) returns a **`token_id`** and a **`token_key`**.

* **`token_key` is your half of the access**, and only yours: we keep the credentials encrypted and a verifier of your key, never the key. Store it as you would a password — encrypted at rest, out of your logs. It is shown once, and **replaced on every renewal**.
* **It is not an encryption key you use.** You never encrypt anything with it: it is one input to the derivation that opens the stored credentials, at the moment of a refresh and inside our runtime. [Credential Tokenization](/guides/credential-tokenization) explains the design, and what losing it means.
* **`token_id` never changes** for the life of a connection — not on a refresh, not on a renewal — so it is the key of your list of connections. [Your list of connections](/flows/financial-rider/recurring-access#your-list-of-connections) explains it.

**Recurring Access.** The ticket, the exchange, and reading a bank again from your backend, on your own schedule and with nobody present.

Recurring Access

**Credential Tokenization.** How the token is built, why neither half opens the credentials alone, and what losing the key means.

Credential Tokenization