> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gethevn.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> HEVN accounts are non-custodial in both products. Self-serve (the General tab): the person who owns the email holds the only key and signs for themselves. Whitelabel (the Whitelabel and REST API tabs): the integrator's customer creates and owns their account through Sign in with HEVN, a HEVN-hosted popup on the integrator's site; the integrator's backend holds a developer key, operates linked accounts and books their payouts, and the account holder approves every payout in that popup. The developer key signs only for the integrator's own wallet; it never signs a client's money.
> Use HEVN's terms exactly. A rail is a bank route a client can request. A virtual account (bnk_) is an opened rail. Account details are what a payer needs in order to wire money, and arrive in a field named requisites. A client is a customer's own account linked to the integrator through Sign in with HEVN (POST /dapi/v1/connect/sessions, then POST /dapi/v1/clients/link); there is no API call that creates one. The business behind it is the integrator's customer. A contact is a saved destination; a beneficiary is the party at the far end of a fiat payout. Every departure of money is a payout, to a bank account or to a wallet alike.
> Never name a banking partner. Say 'a licensed banking partner'.
> There are no published fee rates, spreads or minimums. Prices come from a quote on a specific payment; direct pricing questions to HEVN rather than inventing numbers.
> The whitelabel API is at /dapi/v1 and takes a developer-key session plus an X-Hevn-Account header. Developer keys have an IP allowlist and no scopes. Remote transfers are at /api/v1/remote_spend_limit and take a remote transfer key (rsl_) in X-Api-Key, bounded by an onchain spend permission the account holder grants. They are different credentials and neither works on the other surface. The HEVN CLI is discontinued.

# Quickstart

> In the sandbox: log in with a developer key, link a client through Sign in with HEVN, fund its wallet, and have its holder approve a payout on chain.

In about twenty minutes you will have a client account linked to yours through the HEVN sign-in window, holding testnet USDC, with one payout its holder approved and HEVN settled on chain. Ten steps, all of them in the sandbox.

## Before you start

* **Create a sandbox account in a browser.** Sign up at
  [`https://sandbox.gethevn.com`](https://sandbox.gethevn.com) with your work email. Sandbox and
  production are separate accounts; nothing you do here touches real money.
* **Create a developer key, in the same browser.** Open **Settings → Partner program → Developer** and create one.
  You set one thing that can never be changed afterwards: the **IP allowlist**, which for this
  quickstart is the egress address of the machine you will run it from. Every later call is
  re-checked against it — a request from an address you did not list is a `403` however correct the
  key and the signature are.
* **Allow your local site in the sign-in window.** On the same tab, in the **Sign-in window** card,
  add `http://localhost:8080` under **Allowed sites** and save. Give the window a title while you are
  there; it is what your test customer will see.
* **Have a second Google account ready.** It plays your customer. It must not have a HEVN sandbox
  account of its own: an existing account answers `already_registered` and can never be linked.
* **Save the private key into your environment.** The browser shows it once. Write it to
  `$HEVN_KEY_PEM` with mode `0600`; HEVN never holds it and cannot show it again. Walk-through:
  [Developer key](https://docs.gethevn.com/whitelabel/developer-key.md).
* **Set the environment and install two dependencies.** Everything after that is code.

| Environment | API base URL | App, in a browser | Chain |
| - | - | - | - |
| Sandbox | `https://sandbox-api.hevn.finance/dapi/v1` | `https://sandbox.gethevn.com` | Base Sepolia |
| Production | `https://api.hevn.finance/dapi/v1` | `https://app.gethevn.com` | Base |

Every example on these pages reads the same variables:

```bash theme={null}
export HEVN_API="https://sandbox-api.hevn.finance/dapi/v1"
export HEVN_EMAIL="integrator@example.com"
export HEVN_KEY_PEM="$HOME/.hevn/developer-key.pem"  # mode 0600, never in your repo
export HEVN_CLIENT_ID="cl_7YQ2Kf3mN8"                # the client a call acts for
```

Three more come out of the first two: `HEVN_ACCESS_TOKEN`, the one-hour token
[logging in](https://docs.gethevn.com/whitelabel/sessions.md) returns; `HEVN_USER_ID`, your own account's `cl_…` id, which the
same response returns as `userId` and which the `/client*` routes need in `X-Hevn-Account` when you
read your own account; and `HEVN_PUBLIC_KEY`, the base64 public half of the PEM, which
`POST /dapi/v1/payouts` takes as `publicKey`.

```bash theme={null}
export HEVN_PUBLIC_KEY=$(openssl ec -in "$HEVN_KEY_PEM" -pubout -outform DER 2>/dev/null \
  | base64 | tr -d '\n')
```

Nothing crosses between the two environments: developer keys, clients and tokens are
per-environment, and a sandbox token is refused in production.

```bash Python theme={null}
python -m venv .venv && source .venv/bin/activate
pip install cryptography httpx
```

```bash Node theme={null}
# Node 20 or newer. Signing and HTTP use node:crypto and global fetch.
node --version
npm init --yes
```

```bash Go theme={null}
go mod init example.com/hevn
# Signing and HTTP use the standard library only: crypto/ecdsa, encoding/pem, net/http.
```

> [!NOTE]
>
> The sandbox is the same API against one emulated partner on Base Sepolia. It runs on shared testnet money, so it is capped: **at most 1 per payout, escrow or payin**, a faucet that sends **at most 5, only to a wallet holding less than 1**, and **60 money calls per account per hour**. Full table in [Limits](https://docs.gethevn.com/whitelabel/reference/limits.md#sandbox).

## Ten steps

**1. Log in**

Two calls: `POST /dapi/v1/auth/challenge` returns a payload built by HEVN, and
`POST /dapi/v1/auth/token` exchanges your signature over it for a session. The helpers this uses —
`load_key`, `public_key_b64`, `sign_bytes`, `sign_payload` — are on
[Developer key](https://docs.gethevn.com/whitelabel/developer-key.md) and [Signing](https://docs.gethevn.com/whitelabel/signing.md).

```python Python theme={null}
import os, secrets, time, httpx


def login(key) -> dict:
    api, email = os.environ["HEVN_API"], os.environ["HEVN_EMAIL"]
    nonce = secrets.randbits(48)
    expiry = int(time.time() * 1000) + 120_000
    proof = f"hevn-developer-key-login:{email}:{nonce}:{expiry}".encode()

    started = httpx.post(f"{api}/auth/challenge", json={
        "email": email,
        "publicKey": public_key_b64(key),
        "nonce": nonce,
        "requestExpiry": expiry,
        "signature": sign_bytes(key, proof),
    })
    started.raise_for_status()
    challenge = started.json()

    minted = httpx.post(f"{api}/auth/token", json={
        "challengeId": challenge["challengeId"],
        "signature": sign_payload(key, challenge["payload"]),
    })
    minted.raise_for_status()
    return minted.json()  # accessToken, refreshToken, expiresIn, userId
```

```javascript Node theme={null}
export async function login(key) {
  const api = process.env.HEVN_API;
  const email = process.env.HEVN_EMAIL;
  const nonce = Number(process.hrtime.bigint() % 281474976710656n);
  const expiry = Date.now() + 120_000;
  const proof = Buffer.from(`hevn-developer-key-login:${email}:${nonce}:${expiry}`);

  const post = async (path, body) => {
    const response = await fetch(`${api}${path}`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(body),
    });
    if (!response.ok) throw new Error(`${path} ${response.status} ${await response.text()}`);
    return response.json();
  };

  const challenge = await post("/auth/challenge", {
    email,
    publicKey: publicKeyB64(key),
    nonce,
    requestExpiry: expiry,
    signature: signBytes(key, proof),
  });

  return post("/auth/token", {
    challengeId: challenge.challengeId,
    signature: signPayload(key, challenge.payload),
  });
}
```

```go Go theme={null}
func Login(key *ecdsa.PrivateKey) (Tokens, error) {
	api, email := os.Getenv("HEVN_API"), os.Getenv("HEVN_EMAIL")
	nonce := rand.Int63n(1 << 48)
	expiry := time.Now().Add(2 * time.Minute).UnixMilli()

	proof := fmt.Sprintf("hevn-developer-key-login:%s:%d:%d", email, nonce, expiry)
	proofSignature, err := SignBytes(key, []byte(proof))
	if err != nil {
		return Tokens{}, err
	}
	publicKey, err := PublicKeyB64(key)
	if err != nil {
		return Tokens{}, err
	}

	var challenge struct{ ChallengeID, Payload string }
	if err := PostJSON(api+"/auth/challenge", map[string]any{
		"email": email, "publicKey": publicKey, "nonce": nonce,
		"requestExpiry": expiry, "signature": proofSignature,
	}, &challenge); err != nil {
		return Tokens{}, err
	}

	payloadSignature, err := SignPayload(key, challenge.Payload)
	if err != nil {
		return Tokens{}, err
	}
	var tokens Tokens
	err = PostJSON(api+"/auth/token", map[string]any{
		"challengeId": challenge.ChallengeID, "signature": payloadSignature,
	}, &tokens)
	return tokens, err
}
```

```json Response theme={null}
{
  "accessToken": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9…",
  "refreshToken": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9…",
  "expiresIn": 3600,
  "userId": "cl_9f2c1ab84d7e4f1fa3c65b0e7d9a2c41"
}
```

Keep both: `export HEVN_ACCESS_TOKEN=…` and `export HEVN_USER_ID=cl_9f2c…`.

The `hevn` / `api` client, `key` and `poll_until` in every tab below are
[the reference client](https://docs.gethevn.com/whitelabel/reference/client.md) — copy it once and the rest of this page
is three lines a step.

**2. Serve a page that opens the sign-in window**

The window is opened by your page and answers to your callback page. For this quickstart both are
static files. Save them in an empty folder and serve it on the origin you allowed:

```html index.html theme={null}
<!doctype html>
<meta charset="utf-8">
<title>HEVN quickstart</title>
<input id="url" placeholder="Paste a session url" size="90">
<button id="open">Open HEVN</button>
<pre id="out"></pre>
<script>
  const out = document.getElementById("out");
  const channel = new BroadcastChannel("hevn-connect");
  channel.onmessage = ({ data }) => { out.textContent = JSON.stringify(data.result, null, 2); };
  document.getElementById("open").onclick = () => {
    window.open(document.getElementById("url").value, "hevn-connect", "popup,width=460,height=720");
  };
</script>
```

```html callback.html theme={null}
<!doctype html>
<meta charset="utf-8">
<title>Signing in…</title>
<script>
  const result = Object.fromEntries(new URLSearchParams(location.search));
  if (result.state) {
    const channel = new BroadcastChannel("hevn-connect");
    channel.postMessage({ state: result.state, result });
    channel.close();
    window.close();
  }
</script>
```

```bash theme={null}
python3 -m http.server 8080
```

Open `http://localhost:8080` in a browser. The popup opens straight from the click, which is what
keeps the browser from blocking it. A real integration fetches the session URL from its own server
inside that click instead of having you paste it — [Sign in with HEVN](https://docs.gethevn.com/whitelabel/sign-in-with-hevn.md)
has that version, and the cookie check this quickstart skips.

**3. Create a link session**

The session is about your own account, so it carries **no** `X-Hevn-Account`.

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/connect/sessions" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"action":"link","redirectUri":"http://localhost:8080/callback.html"}' | jq -r .url
```

```python Python theme={null}
session = hevn.post("/connect/sessions", {
    "action": "link", "redirectUri": "http://localhost:8080/callback.html",
})
print(session["url"])
```

```javascript Node theme={null}
const session = await hevn.post("/connect/sessions", {
  action: "link", redirectUri: "http://localhost:8080/callback.html",
});
console.log(session.url);
```

```go Go theme={null}
session, err := api.Post("/connect/sessions", hevn.Body{
	"action": "link", "redirectUri": "http://localhost:8080/callback.html",
})
fmt.Println(session.Str("url"))
```

```json Response theme={null}
{ "url": "https://sandbox.gethevn.com/connect?session=eyJ…", "state": "pQ8v2Zr0…", "expiresAt": "2026-09-17T10:14:11Z" }
```

The URL works for about ten minutes. `403 redirect_uri_not_allowed` means `http://localhost:8080`
is not saved under **Allowed sites**. `403 client_creation_not_enabled` means your account is not
an active integrator yet; ask HEVN before you write any more code — see
[Going live](https://docs.gethevn.com/whitelabel/going-live.md#what-hevn-must-enable).

**4. Sign in as your customer**

Paste the URL into the page and click **Open HEVN**. In the popup, sign in with the second Google
account and choose **Allow**. The popup closes and the page shows what came back:

```json theme={null}
{ "code": "eyJhbGciOiJFUzI1NiIs…", "state": "pQ8v2Zr0…" }
```

`state` is the one `POST /connect/sessions` answered. The code lives for five minutes.

**5. Exchange the code for a client**

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/clients/link" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"code":"eyJhbGciOiJFUzI1NiIs…"}' | jq '{id, status, baseSmartWallet}'
```

```python Python theme={null}
client = hevn.post("/clients/link", {"code": code})
northwind = hevn.acting_as(client["id"])
ready = poll_until(lambda: northwind.get("/client"),
                   lambda seen: seen["status"] != "provisioning")
```

```javascript Node theme={null}
const client = await hevn.post("/clients/link", { code });
const northwind = hevn.actingAs(client.id);
const ready = await pollUntil(() => northwind.get("/client"),
  (seen) => seen.status !== "provisioning");
```

```go Go theme={null}
client, err := api.Post("/clients/link", hevn.Body{"code": code})
northwind := api.ActingAs(client.Str("id"))
ready, err := hevn.PollUntil(
	func() (hevn.Payload, error) { return northwind.Get("/client") },
	func(seen hevn.Payload) bool { return seen.Str("status") != "provisioning" },
	2*time.Minute,
)
```

```json Response theme={null}
{ "id": "cl_7YQ2Kf3mN8", "status": "ready", "baseSmartWallet": "0x2f1c9d0b8a7e6532d41ac9f0b3e27d5581a4c6f9" }
```

Keep the id: `export HEVN_CLIENT_ID=cl_7YQ2Kf3mN8`. If it says `provisioning`, read
`GET /dapi/v1/client` with the id in `X-Hevn-Account` every two seconds until it is `ready`.

**6. Fund the client's wallet**

The sandbox mints emulated money. Without `bankId` this is a token transfer straight to the
client's wallet; the `X-Hevn-Account` header that `northwind` carries is what makes the call act
for the client.

```bash cURL theme={null}
curl -X POST "$HEVN_API/sandbox/deposits" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  -H "Idempotency-Key: quickstart-deposit-1" \
  -H "Content-Type: application/json" -d '{"amount":"5.00","token":"USDC"}'
```

```python Python theme={null}
credit = northwind.post("/sandbox/deposits", {"amount": "5.00", "token": "USDC"},
                        idempotency_key="quickstart-deposit-1")
```

```javascript Node theme={null}
const credit = await northwind.post("/sandbox/deposits",
  { amount: "5.00", token: "USDC" },
  { idempotencyKey: "quickstart-deposit-1" });
```

```go Go theme={null}
credit, err := northwind.Post("/sandbox/deposits", hevn.Body{"amount": "5.00", "token": "USDC"},
	hevn.IdempotencyKey("quickstart-deposit-1"))
```

A `200` means the tokens landed; a `202` means they are on their way and the same request,
with the same key, is safe to repeat. The faucet sends at most `5` per call, and only to a
wallet that holds less than `1` of that token ([Sandbox limits](https://docs.gethevn.com/whitelabel/sandbox.md#limits)).

**7. Read the balance you just created**

`/client/balance` names no client in its path either: the header picks the account, and leaving
it out is a `422` for a missing header rather than a read of your own wallet.

```bash cURL theme={null}
curl -s "$HEVN_API/client/balance" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_CLIENT_ID" | jq
```

```python Python theme={null}
balance = northwind.get("/client/balance")
```

```javascript Node theme={null}
const balance = await northwind.get("/client/balance");
```

```go Go theme={null}
balance, err := northwind.Get("/client/balance")
```

```json Response theme={null}
{
  "clientId": "cl_7YQ2Kf3mN8",
  "baseSmartWallet": "0x2f1c9d0b8a7e6532d41ac9f0b3e27d5581a4c6f9",
  "accounts": [{ "account": "USDC", "balance": "5.000000", "usdValue": "5.00" }],
  "totalUsd": "5.00"
}
```

That balance is read from Base Sepolia, not from a HEVN table.

**8. Save a contact and book a payout**

Pay the money to an address you control: your own account's wallet. Read it with the same
balance route, acting as yourself — the `userId` from step 1 goes in `X-Hevn-Account`, because
that header is the only thing that selects an account on `/client*`. Then book `1.00` to it.

```bash cURL theme={null}
export HEVN_OWN_WALLET=$(curl -s "$HEVN_API/client/balance" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_USER_ID" | jq -r .baseSmartWallet)

export HEVN_CONTACT_ID=$(curl -s -X POST "$HEVN_API/contacts" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  -H "Content-Type: application/json" \
  -d "{\"name\":\"Treasury\",\"crypto\":{\"chain\":\"base\",\"token\":\"USDC\",\"address\":\"$HEVN_OWN_WALLET\"}}" | jq -r .id)

curl -s -X POST "$HEVN_API/payouts" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  -H "Idempotency-Key: quickstart-payout-1" -H "Content-Type: application/json" \
  -d "{\"contactId\":\"$HEVN_CONTACT_ID\",\"amount\":\"1.00\"}"
```

```python Python theme={null}
own_wallet = hevn.acting_as(user_id).get("/client/balance")["baseSmartWallet"]
contact = northwind.post("/contacts", {
    "name": "Treasury",
    "crypto": {"chain": "base", "token": "USDC", "address": own_wallet},
})
payout = northwind.post("/payouts", {"contactId": contact["id"], "amount": "1.00"},
                        idempotency_key="quickstart-payout-1")
```

```javascript Node theme={null}
const { baseSmartWallet: ownWallet } = await hevn.actingAs(userId).get("/client/balance");
const contact = await northwind.post("/contacts", {
  name: "Treasury",
  crypto: { chain: "base", token: "USDC", address: ownWallet },
});
const payout = await northwind.post("/payouts", { contactId: contact.id, amount: "1.00" },
  { idempotencyKey: "quickstart-payout-1" });
```

```go Go theme={null}
own, err := api.ActingAs(userID).Get("/client/balance")
contact, err := northwind.Post("/contacts", hevn.Body{
	"name":   "Treasury",
	"crypto": hevn.Body{"chain": "base", "token": "USDC", "address": own.Str("baseSmartWallet")},
})
payout, err := northwind.Post("/payouts", hevn.Body{"contactId": contact.Str("id"), "amount": "1.00"},
	hevn.IdempotencyKey("quickstart-payout-1"))
```

```json Response theme={null}
{
  "id": "po_6d21…",
  "kind": "onchain",
  "status": "awaitingSignature",
  "idempotencyKey": "quickstart-payout-1",
  "quote": { "fromAmount": "1.00", "fromCurrency": "USDC", "toAmount": "1.00", "toCurrency": "USDC" },
  "authorization": "client",
  "contact": { "contactId": "ct_3K9…", "name": "Treasury",
               "address": "0x8f3bd1c7a0e94f25b6d1c83a5e07f4b219ad6e80",
               "chainId": "base", "token": "USDC" }
}
```

`authorization: "client"` and no `approval`: this is the client's money, so its holder signs it,
not your key. Keep the id: `export HEVN_PAYOUT_ID=po_6d21…`. `quickstart-payout-1` names this
payment — re-run the call with the same key and you replay it; change the key and you book twice.

**9. Approve it in the sign-in window**

Create a `sign` session for the payout, paste its URL into the page and click **Open HEVN** again.

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/connect/sessions" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d "{\"action\":\"sign\",\"payoutId\":\"$HEVN_PAYOUT_ID\",\"redirectUri\":\"http://localhost:8080/callback.html\"}" \
  | jq -r .url
```

```python Python theme={null}
session = hevn.post("/connect/sessions", {
    "action": "sign", "payoutId": payout["id"],
    "redirectUri": "http://localhost:8080/callback.html",
})
```

```javascript Node theme={null}
const session = await hevn.post("/connect/sessions", {
  action: "sign", payoutId: payout.id,
  redirectUri: "http://localhost:8080/callback.html",
});
```

```go Go theme={null}
session, err := api.Post("/connect/sessions", hevn.Body{
	"action": "sign", "payoutId": payout.Str("id"),
	"redirectUri": "http://localhost:8080/callback.html",
})
```

Sign in with the same second Google account. The window shows `1.00 USDC`, the contact `Treasury`
and your title, and the holder's wallet signs the debit. The page then shows:

```json theme={null}
{ "status": "submitted", "payout": "po_6d21…", "reference": "0x…", "state": "…" }
```

**10. Read the payout and the ledger**

The redirect said `submitted`. The payout read is the record:

```bash cURL theme={null}
curl -s "$HEVN_API/payouts/$HEVN_PAYOUT_ID" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  | jq '{status, transactionHash}'
```

```python Python theme={null}
done = poll_until(lambda: northwind.get(f"/payouts/{payout['id']}"),
                  lambda seen: seen["status"] not in ("awaitingSignature", "submitted"))
rows = northwind.get("/transactions", {"limit": 5})["items"]
```

```javascript Node theme={null}
const done = await pollUntil(() => northwind.get(`/payouts/${payout.id}`),
  (seen) => !["awaitingSignature", "submitted"].includes(seen.status));
const { items: rows } = await northwind.get("/transactions", { limit: 5 });
```

```go Go theme={null}
done, err := hevn.PollUntil(
	func() (hevn.Payload, error) { return northwind.Get("/payouts/" + payout.Str("id")) },
	func(seen hevn.Payload) bool { s := seen.Str("status"); return s != "awaitingSignature" && s != "submitted" },
	2*time.Minute,
)
page, err := northwind.Get("/transactions", hevn.Query{"limit": "5"})
```

```json Response theme={null}
{ "status": "settled", "transactionHash": "0x3a77…" }
```

The ledger row carries its amounts as two sides — `from` is what left the source, `to` is what
arrived — rather than a single `amount`. On this payout both are `1.000000 USDC`, and the same
1 USDC is now in your own account's balance: read it with the balance call from step 7, with
`$HEVN_USER_ID` in `X-Hevn-Account`.

## What just happened

* **Your customer owns the account.** They created it in the sign-in window with their own Google
  sign-in, and allowed your product to act on it. You got a `cl_…` id, never their credentials.
* **Your key logged you in; it never touched their money.** The payout was booked by you and signed by
  the holder's own wallet. See [Accounts and control](https://docs.gethevn.com/whitelabel/accounts-and-control.md).
* **The money is real testnet money.** It moved on Base Sepolia; `transactionHash` is on chain.
* **Every constant came out of an earlier response.** Client id, wallet address, contact id, payout
  id — nothing was invented.
* **One key was yours to choose:** the `Idempotency-Key`. It names the payment, so a retry is the
  same payment. See [Conventions](https://docs.gethevn.com/whitelabel/conventions.md#idempotency-key).

## What this quickstart did not do

It skipped the server half of the sign-in: binding the session's `state` to the browser and keeping
the client id in your own session. Do that before anything else — [Sign in with HEVN](https://docs.gethevn.com/whitelabel/sign-in-with-hevn.md).

It never touched fiat either. Getting the client its own account details, and paying a bank account with
a currency conversion, needs a client the partner has approved: the KYB document filled and its rail open.
That is the next piece of work — [Onboard a client](https://docs.gethevn.com/whitelabel/onboarding.md), then
[Virtual accounts](https://docs.gethevn.com/whitelabel/virtual-accounts.md) and [Payouts](https://docs.gethevn.com/whitelabel/payouts.md).

> [card] [Next: Accounts and control](https://docs.gethevn.com/whitelabel/accounts-and-control.md)
>
> Who owns a client account, and what your key can and cannot do.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.