Skip to navigation

Credential Tokenization

Run an access again next month without either side holding enough to open it
View as Markdown

Refreshing somebody’s data every month should not mean keeping their bank password in your database. Tokenization moves that problem off your side without moving it wholly onto ours: the credentials are stored encrypted by the platform, under a key that only exists while you present the token you hold.

Neither half is enough on its own. That is the whole design, and it is worth understanding before you decide whether to use it — because it also decides what happens when something goes wrong.

Tokenization is enabled per application. Ask support to turn it on. It is available for direct executions and for Financial Rider, where it is what recurring access is built on — and this page stays the single explanation of how it works. It is also per engine: only an engine whose tokenized_access is true in List your engines can keep credentials — typically banks, telcos and utilities, sources worth reading again.


The two halves

1

You ask for a ticket

When you run an access for the first time, you ask for it to be tokenized — one setting, wherever the credentials are first typed: base_configurations.tokenized_access when you start a direct execution, or settings.tokenized_access when you create a Financial Rider session. What comes back is a ticket: single-use, short-lived, and useless for anything except the next step.

2

You exchange it, once

The exchange returns a token id and a token key — a 64-character random string, generated at that moment. It is handed to you exactly once and it is never shown again, by any screen, endpoint or person.

3

From then on, you send the token

Every later run presents the pair instead of a password. The credentials themselves never travel back to you, and never travel to you at all.

WhoHoldsWhat it is
Youtoken_id + token_keyThe key half. Store it as you would a password: encrypted at rest, out of your logs.
INFONITEThe encrypted credentials, and a verifier of your keyCiphertext, plus enough to check that a presented key is the right one — never the key itself.

How the encryption works

This is the part that makes the split real rather than a promise.

1

The key you hold is not the encryption key

Your token_key is never used directly to encrypt anything. It is one input to a derivation: HMAC-SHA256 over your key, under a secret that lives only in our runtime environment. The result is the 256-bit key that actually opens the credentials, and it exists only for the moment it is used — it is never written down, on either side.

2

The credentials are sealed with it

AES-256-GCM, authenticated encryption: a byte altered in storage makes the decryption fail loudly instead of returning something plausible.

3

Your key is stored as a verifier, not as a key

What we keep beside the ciphertext is a scoped, non-reversible HMAC of your token key, compared in constant time when you present it. It proves the key is right. It cannot produce the key, and it cannot open anything.

your token_key HMAC-SHA256 our environment secret AES-256 key(exists only at use time) AES-GCM the stored credentials

What that means in practice

ScenarioCan the credentials be read?
We hold the ciphertext, you hold your keyYes — during your call, and only then.
Somebody obtains our stored data aloneNo. The derivation needs your token key, which is not there.
Somebody obtains your token key aloneNo. There is nothing to decrypt without our stored data.
Somebody obtains both — ciphertext and keyStill not, without our environment’s secret and the exact derivation. That is the third factor, and it lives in neither of the two places an attacker would look.
You lose your token keyNobody can. Not you, not us, not support. See below.

This is shared responsibility, in the exact sense of the term. You are not handing us your customers’ credentials to look after; you are keeping the half that makes them readable. It also means the protection is only as good as how you store token_key — the one thing we cannot do for you.


Losing the key is final, and that is deliberate

There is no recovery path, no support ticket and no override. If the token key is lost, the stored credentials become bytes nobody can open — including us.

Final for the key — not for the connection. Losing the key loses the ability to refresh, nothing else: start a new session for the same customer_id, have your customer connect the same account, and exchange its ticket. The answer carries the same token_id with a new key, so the entry you keep for that connection — and everything you hang off it — goes on working. That is the only way back, and it involves your customer, because it involves their password. How to build it.

Design for that before it happens. A token key living in exactly one place, with no backup and no rotation plan, turns a routine database migration into a re-onboarding of every customer. Store it as you store any other secret you cannot recreate.


What a token can do, and for how long

A token is not a general-purpose credential. Its scope is fixed at the exchange, and a run can narrow it but never widen it:

Locked toMeaning
One applicationYours. The token is meaningless without your application secret alongside it.
One sourceThe engine that produced it.
One customerThe customer_id of the run whose ticket you exchanged.
A set of featuresAsk for fewer on a given run if you like; asking for one outside the original scope is refused.

It expires 90 days after the exchange, and using it does not extend that. When the date arrives, the stored credentials are deleted. Track it and renew the connection before it lapses — a quarterly job that never renews works for three months and then fails for everybody at once.

A token can also be locked by the source: if the institution rejects the stored credentials or blocks the account, the token stops running and says so. Retrying does not help and can make it worse — institutions count failed attempts. That is a conversation with your customer, and it ends with them connecting the account again.

One token per connection. A connection is your application, one source, one customer_id and one account at that source. Exchanging a ticket for a connection that already has a token renews it — the same token_id, a new key, the new run’s scope and expiry, renewed: true — instead of minting a second one. A renewal revokes nothing: token_id never changes for the life of a connection. Managing your customers’ connections turns that into a list you can show.


Managing your customers’ connections

A practical guide: what to keep on your side, and how to turn it into the screen your customer uses — BBVA, connected · Santander, needs your attention — with connect, reconnect and remove.

The rule underneath is one sentence: a connection is one row, and token_id is its key. It is the one field that never changes for the life of a connection — not on a refresh, not on a lock, not on a reconnection.

Everything after the first connection is the Direct Executions API, server to server. Exchanging a ticket, refreshing, checking and revoking a token are calls your backend makes with your application secret — Exchange a ticket, Initiate with a token, Check a token and Revoke a token. The widget and its API play no part in them: your customer is only needed to connect, or to reconnect.

What to keep, one row per connection

KeepFrom the Exchange a ticket answerWhy
token_idtoken.token_idThe row’s key. Never changes; every later answer about this connection carries it.
token_keytoken.token_keyYour half of the access. Encrypted at rest, out of your logs — and replaced on every renewal.
customer_idcontext.customer_idWhose connection it is. Reconnections must use this same value.
engine_referencecontext.engine_referenceWhich source: its name, for the screen, comes from List your engines.
status_code, date_expirationCheck a tokenWhat the row says, and when to ask for a reconnection.
source_execution_idcontext.source_execution_idOptional: which session connected it last, for your own audit trail.

Never keep the password — you never had it, which is the point — nor the ticket once it is exchanged.

Connect, reconnect, remove

1

Connect: your customer adds a bank

Start a session for that customer’s customer_id — a Financial Rider session with tokenized_access, or a direct execution that asks for a ticket — and exchange the ticket for each connection at Exchange a ticket. An answer with renewed: false is a new row.

2

Reconnect: a lock, a changed password, an expiry coming

Exactly the same, with the same customer_id. Your customer signs in to the same account, and Exchange a ticket answers the same token_id, a new key and renewed: true: find the row by token_id, replace its key, and it is connected again. Nothing is revoked: the previous key simply stops working, and the row, its history and anything you hang off it stay.

3

Remove: your customer disconnects a bank, or leaves

Revoke the token and delete the row — and the executions that used it, when they withdraw their consent. That connection is over: connecting that account again later is a new row, with a new token_id.

A different username is a different account, so it is a new row even at the same bank — your customer may well have two. If a reconnect comes back with a new token_id, they signed in to another account: show it as a new connection, and leave the old one to be reconnected or removed.

What each row shows

Check a token saysShowOffer
T10N_OKConnected — with the date of your last refresh, which is yours to keepRemove
T10N_KOThe last refresh did not completeNothing yet — the next refresh retries. If it keeps failing, Reconnect
T10N_LOGIN_LOCKNeeds your attention: the bank refused the stored credentialsReconnect — and stop refreshing it: retrying counts against your customer at the bank
T10N_SYSTEM_LOCKPaused for nowNothing: it is ours, and it passes
date_expiration within a couple of weeksExpires on that dateReconnect
T10N_REVOKED, or 404—Delete the row

The whole rule in code

Every Exchange a ticket answer is an upsert keyed by token_id:

def keep(rows: dict, exchanged: dict) -> str:
"""`rows` maps token_id -> what you keep for that connection. Returns what happened."""
token, context = exchanged["token"], exchanged["context"]
rows[token["token_id"]] = {
"token_key": encrypt(token["token_key"]), # yours to protect: it is never shown again
"customer_id": context["customer_id"],
"engine_reference": context["engine_reference"],
"source_execution_id": context["source_execution_id"],
}
return "reconnected" if exchanged["renewed"] else "connected"

There is nothing to deduplicate and nothing to compare yourself — not locking_hash, not the bank’s name: the exchange has already decided whether this is a connection you have. An exchange that answers 409 overlapped another of the same ticket — a retry: wait for that one, or retry after its Retry-After.

If you lose a key

You lose the refresh of that connection, not the connection. Start a new session for the row’s customer_id and have your customer connect the same account: the exchange answers the row’s own token_id with a new key, and the row works again — keep the new key somewhere you cannot lose it this time. Under a different customer_id, the same account would be another connection, and the old row would stay without a key.


Revoking

Revocation is immediate and irreversible: no execution can use the token afterwards, and the stored credentials are marked for deletion. Call it when a customer withdraws consent, closes their account with you, or asks you to stop — and after any suspicion that your half leaked.

Your customer’s right to be forgotten is two calls, not a support ticket. Revoke the token, delete the executions that used it. Both are immediate, and neither needs us.


Where to do all of this