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

# Global Accounts

> Self-custody stablecoin accounts that plug into Grid payment flows

A Grid Global Account is powered by a self-custody embedded [Spark](https://spark.money) wallet Grid provisions for your customer that holds a stablecoin or BTC balance and participates in the standard Grid payment flows. It behaves like any other internal account for *incoming* funds, but every outbound transfer must be authorized by the customer — a session signing key issued for their device signs each payment. In the API, a Global Account is an internal account with `type: "EMBEDDED_WALLET"` that participates in the standard Grid customer, quote, transaction, and webhook flows.

## Why a Grid Global Account?

* **Self-custody.** Grid never has unilateral access to move user funds, and neither do you. The customer's device is the only party that can authorize a transaction.
* **Stablecoin-denominated.** Balances are held as stablecoins like [Brale-issued USDB](https://brale.xyz/stablecoins/USDB). Use the standard `/quotes` API to convert in from fiat or out to any supported Grid bank-account rail (ACH, PIX, CLABE, UPI, IBAN, UMA, …).
* **Grid-native.** You reuse the customer, internal-account, quote, transaction, and webhook primitives you already integrated for Payouts or P2P. The only thing that's new is an auth + signing layer at the account.
* **Built on Bitcoin.** Global Accounts run on Spark, a Lightning-compatible Bitcoin L2 that supports instant, low-fee Bitcoin and Stablecoin transfers. You get the benefits of running on Bitcoin, the most neutral, decentralized, and secure network for money.

## Payment flow

Grid Global Accounts ride on the same `/quotes` + `/quotes/{id}/execute` pattern as every other Grid payment. The only thing that's different is that outbound transfers need a client signature.

* **Incoming funds.** Funding an account works like any other internal account. Create a quote with the Global Account as the `destination`, execute it, and Grid converts the source currency into USDB and credits the account. No customer approval needed — incoming value is passive.
* **Outgoing funds.** Withdrawals and transfers out require the customer to authorize them on their device. Grid returns a `payloadToSign` in the quote's `paymentInstructions`; the client signs those bytes with its session signing key and passes the base64 signature as the `Grid-Wallet-Signature` header on `/quotes/{id}/execute`. Only then does Grid release the funds.

Sessions are short-lived (15 minutes by default) and bound to a specific device via the client key pair, so a stolen signature can't be replayed from a different device or after the session expires. Standard transaction webhooks fire throughout the lifecycle — see [Transaction lifecycle](/platform-overview/core-concepts/transaction-lifecycle).

## Architecture

Three parties participate in every signed action:

| Party                  | Role                                                                                                                                                                   |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Client**             | The customer's device (browser, iOS app, or Android app). Generates the client key pair, runs WebAuthn, decrypts the session signing key, and signs outbound requests. |
| **Integrator backend** | Your server. Holds your Grid API credentials, brokers every call to Grid on behalf of the client, and issues WebAuthn challenges for initial passkey registration.     |
| **Grid**               | Verifies auth credentials, issues session signing keys (encrypted to the client's public key), and enforces that every account action is authorized.                   |

The client **never** talks to Grid directly. Every request flows client → integrator backend → Grid.

## Auth credentials, client keys, and session signing keys

Three distinct pieces of crypto collaborate to authorize actions on the Global Account (withdrawals, credential changes, session revocations, wallet exports, and wallet privacy updates):

| Piece                                                   | Where it lives                                                                                                              | How long it lives                                     | What it proves                                                                                                                                                                                                                                                               |
| ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Auth credential** — passkey, OIDC token, or email OTP | Registered on the account; the passkey itself lives on the authenticator, OIDC on your IdP, OTP in the user's inbox         | Until the customer revokes it                         | *"I am the human who owns this account."* Used to authenticate the user at the start of each session.                                                                                                                                                                        |
| **Client key pair** (P-256)                             | Generated on the client device for each session-issuing or export request; private key stays in device-local secure storage | One authentication, session refresh, or wallet export | Binds a given session signing key or wallet export delivery to the exact device that asked for it — Grid encrypts the response to this public key, so only this device can decrypt.                                                                                          |
| **Session signing key** (P-256)                         | Issued by Grid, sealed to the client public key, decrypted and held on the device for the session's lifetime                | 15 minutes (default)                                  | *"This specific account action was approved on an authenticated device."* Builds Turnkey API-key stamps over the `payloadToSign` Grid returns on quotes, credential changes, session refresh/revocation, wallet exports, customer email updates, and wallet privacy updates. |

The flow is always the same: verify an auth credential → receive a short-lived session signing key → build a Turnkey API-key stamp over the `payloadToSign` bytes on the client → pass that stamp as the `Grid-Wallet-Signature` header on the request that actually moves funds or changes account state. This applies to withdrawals, adding or removing credentials, refreshing or revoking sessions, exporting the wallet seed, updating customer email for tied email OTP credentials, and updating wallet privacy.

## Prerequisites

<Warning>
  Customers who hold a Global Account must be KYC/KYB verified before any account funds can move from or to fiat rails. This quickstart picks up after KYC is complete.

  In sandbox, customers are automatically KYC approved on creation so you can skip straight to account setup.
</Warning>

You also need:

* A platform configured with `USDB` in its supported currencies. In sandbox, USDB is enabled by default alongside `USD` and `USDC`.
* Sandbox or production API credentials with access to the `Embedded Wallet Auth` and `Internal Accounts` endpoints.

```bash theme={null}
export GRID_BASE_URL="https://api.lightspark.com/grid/2025-10-13"
export GRID_CLIENT_ID="YOUR_SANDBOX_CLIENT_ID"
export GRID_CLIENT_SECRET="YOUR_SANDBOX_CLIENT_SECRET"
```

## Walkthrough

The walkthrough below is the happy path: create a customer, find the auto-provisioned account and its default email OTP credential, fund it, and withdraw to a bank account. Each step shows the HTTP request your integrator backend makes on behalf of the client.

### 1. Create a customer

Create the customer record. A Global Account is provisioned automatically whenever a customer is created on a platform that has `USDB` in its supported currencies — you don't need to pass it on the customer.

```bash theme={null}
curl -X POST "$GRID_BASE_URL/customers" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "customerType": "INDIVIDUAL",
    "platformCustomerId": "ind-9f84e0c2",
    "region": "US",
    "email": "jane@example.com",
    "fullName": "Jane Doe",
    "birthDate": "1990-01-15",
    "nationality": "US"
  }'
```

**Response:** `201 Created` with the new `Customer:...` id. In sandbox, the customer is KYC-approved immediately; in production you would now run them through the KYC / KYB flow before any funds can move.

### 2. Find the Global Account

When a customer is created on a USDB-enabled platform, Grid automatically provisions a Global Account alongside their other internal accounts. Fetch it by filtering the customer's internal accounts by `type=EMBEDDED_WALLET`.

```bash theme={null}
curl -X GET "$GRID_BASE_URL/internal-accounts?customerId=Customer:019542f5-b3e7-1d02-0000-000000000001&type=EMBEDDED_WALLET" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
```

**Response:**

```json theme={null}
{
  "data": [
    {
      "id": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
      "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
      "type": "EMBEDDED_WALLET",
      "balance": {
        "amount": 0,
        "currency": {
          "code": "USDB",
          "name": "USDB",
          "decimals": 6
        }
      },
      "fundingPaymentInstructions": [],
      "createdAt": "2026-04-19T12:00:00Z",
      "updatedAt": "2026-04-19T12:00:00Z"
    }
  ],
  "hasMore": false,
  "totalCount": 1
}
```

Hold onto the `InternalAccount:...` id — every auth credential is scoped to it.

### 3. Find the default email OTP credential

Global Accounts are initialized with an `EMAIL_OTP` credential tied to the customer email on file. Fetch the auth methods for the account and keep the `AuthMethod:...` id for the signing step later in this walkthrough.

```bash theme={null}
curl -X GET "$GRID_BASE_URL/auth/credentials?accountId=InternalAccount:019542f5-b3e7-1d02-0000-000000000002" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
```

**Response:**

```json theme={null}
{
  "data": [
    {
      "id": "AuthMethod:019542f5-b3e7-1d02-0000-000000000001",
      "accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
      "type": "EMAIL_OTP",
      "nickname": "jane@example.com",
      "createdAt": "2026-04-19T12:00:01Z",
      "updatedAt": "2026-04-19T12:00:01Z"
    }
  ]
}
```

You can add passkeys or OAuth credentials later, but adding credentials is itself a signed action. Start with the default email OTP credential to mint the first session signing key.

### 4. Fund the Account

Global Accounts behave like any other internal account on the way in — incoming funds do not need the customer's signature. In sandbox, use the sandbox funding endpoint to skip straight to a funded state:

```bash theme={null}
curl -X POST "$GRID_BASE_URL/sandbox/internal-accounts/InternalAccount:019542f5-b3e7-1d02-0000-000000000002/fund" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1000000000
  }'
```

`amount` is in the smallest unit of the account's currency. USDB has 6 decimals, so `1000000000` is 1,000.00 USDB.

You will receive an `INCOMING_PAYMENT` webhook when the balance updates. The account now holds 1,000.00 USDB.

<Tip>
  To fund from another currency (USD ACH, USDC on-chain, etc.), create a quote with `destination.destinationType: "ACCOUNT"` pointing at the Global Account's `InternalAccount` id. The quote's `sourceCurrency` can be any supported platform currency; Grid will convert into USDB on execute.
</Tip>

### 5. Add an external bank account

Add the destination the customer wants to withdraw to. This is a standard external account — nothing Global Account-specific.

```bash theme={null}
curl -X POST "$GRID_BASE_URL/customers/external-accounts" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "currency": "USD",
    "platformAccountId": "jane_doe_checking",
    "accountInfo": {
      "accountType": "USD_ACCOUNT",
      "accountNumber": "1234567890",
      "routingNumber": "021000021",
      "beneficiary": {
        "beneficiaryType": "INDIVIDUAL",
        "fullName": "Jane Doe",
        "birthDate": "1990-01-15",
        "nationality": "US",
        "address": {
          "line1": "123 Main Street",
          "city": "San Francisco",
          "state": "CA",
          "postalCode": "94105",
          "country": "US"
        }
      }
    }
  }'
```

**Response:** `201 Created` with the new `ExternalAccount:...` id.

### 6. Create a withdrawal quote

Create a quote with the Global Account as the source. Grid returns a `payloadToSign` in the quote's payment instructions — this is what the client will sign to authorize the transfer.

```bash theme={null}
curl -X POST "$GRID_BASE_URL/quotes" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "sourceType": "ACCOUNT",
      "accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002"
    },
    "destination": {
      "destinationType": "ACCOUNT",
      "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"
    },
    "lockedCurrencySide": "SENDING",
    "lockedCurrencyAmount": 10000000,
    "description": "Withdrawal to checking"
  }'
```

`lockedCurrencyAmount` is in the smallest unit of the locked side's currency. Here the sending currency is USDB (6 decimals), so `10000000` is 10.00 USDB.

**Response:**

```json theme={null}
{
  "id": "Quote:019542f5-b3e7-1d02-0000-000000000006",
  "status": "PENDING",
  "createdAt": "2026-04-19T12:05:00Z",
  "expiresAt": "2026-04-19T12:10:00Z",
  "source": {
    "sourceType": "ACCOUNT",
    "accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002"
  },
  "destination": {
    "destinationType": "ACCOUNT",
    "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"
  },
  "sendingCurrency": { "code": "USDB", "name": "USDB", "decimals": 6 },
  "receivingCurrency": { "code": "USD", "name": "United States Dollar", "symbol": "$", "decimals": 2 },
  "totalSendingAmount": 10000000,
  "totalReceivingAmount": 975,
  "exchangeRate": 1.0,
  "feesIncluded": 250000,
  "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000005",
  "paymentInstructions": [
    {
      "accountOrWalletInfo": {
        "accountType": "EMBEDDED_WALLET",
        "payloadToSign": "{\"type\":\"ACTIVITY_TYPE_SIGN_TRANSACTION_V2\",\"timestampMs\":\"1746736509954\",\"organizationId\":\"org_abc123\",\"parameters\":{\"signWith\":\"wallet_abc123def456\",\"unsignedTransaction\":\"ea69b4bf05f775209f26ff0a34a05569180f7936579d5c4af9377ae550194f72\",\"type\":\"TRANSACTION_TYPE_ETHEREUM\"},\"generateAppProofs\":true}"
      },
        "instructionsNotes": "Stamp the payloadToSign byte-for-byte and pass the stamp as the Grid-Wallet-Signature header on execute"
    }
  ]
}
```

### 7. Authenticate and sign

The customer has an outstanding quote with a `payloadToSign`. Now we need a session signing key to sign it with. With `EMAIL_OTP`, the client generates a TEK (Target Encryption Key) pair, HPKE-encrypts the OTP code, and uses the TEK private key both to complete login and to sign the quote payload.

<Steps>
  <Step title="Your backend requests a fresh OTP">
    Ask Grid to send a fresh OTP email for the default `EMAIL_OTP` credential. The response includes `otpEncryptionTargetBundle` for the secure OTP flow.

    ```bash theme={null}
    curl -X POST "$GRID_BASE_URL/auth/credentials/AuthMethod:019542f5-b3e7-1d02-0000-000000000001/challenge" \
      -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
    ```

    **Response (200):**

    ```json theme={null}
    {
      "id": "AuthMethod:019542f5-b3e7-1d02-0000-000000000001",
      "accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
      "type": "EMAIL_OTP",
      "nickname": "jane@example.com",
      "otpEncryptionTargetBundle": "{\"version\":\"v1.0.0\",\"data\":\"7b22...\",\"dataSignature\":\"3045...\",\"enclaveQuorumPublic\":\"04a1...\"}",
      "createdAt": "2026-04-19T12:00:01Z",
      "updatedAt": "2026-04-19T12:05:00Z"
    }
    ```

    Return `otpEncryptionTargetBundle` to the client.
  </Step>

  <Step title="Client encrypts the OTP and verifies">
    The client generates a fresh <a href="client-keys#1-generate-a-client-key-pair">P-256 key pair (the TEK)</a>, <a href="client-keys#encrypt-the-otp-code-email_otp-only">HPKE-encrypts</a> `{otp_code, public_key}` under `otpEncryptionTargetBundle`, and sends the encrypted bundle to your backend. In sandbox, use OTP code `000000`.

    Your backend calls verify with the encrypted bundle:

    ```bash theme={null}
    curl -X POST "$GRID_BASE_URL/auth/credentials/AuthMethod:019542f5-b3e7-1d02-0000-000000000001/verify" \
      -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
      -H "Content-Type: application/json" \
      -d '{
        "type": "EMAIL_OTP",
        "encryptedOtpBundle": "{\"encappedPublic\":\"044f631a...\",\"ciphertext\":\"1fa1023390...\"}"
      }'
    ```

    **Response (202):**

    ```json theme={null}
    {
      "type": "EMAIL_OTP",
      "payloadToSign": "eyJhbGciOiJFUzI1NiIsImtpZCI6InR1cm5rZXkifQ...",
      "requestId": "Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21",
      "expiresAt": "2026-04-19T12:10:00Z"
    }
    ```

    Return `payloadToSign` and `requestId` to the client.
  </Step>

  <Step title="Client signs the verification token and completes login">
    The client <a href="client-keys#4-sign-a-payloadtosign">stamps</a> `payloadToSign` with the TEK private key and sends the stamp back to your backend.

    Your backend retries the same request with the stamp:

    ```bash theme={null}
    curl -X POST "$GRID_BASE_URL/auth/credentials/AuthMethod:019542f5-b3e7-1d02-0000-000000000001/verify" \
      -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
      -H "Content-Type: application/json" \
      -H "Grid-Wallet-Signature: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9" \
      -H "Request-Id: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21" \
      -d '{
        "type": "EMAIL_OTP",
        "encryptedOtpBundle": "{\"encappedPublic\":\"044f631a...\",\"ciphertext\":\"1fa1023390...\"}"
      }'
    ```

    **Response (200):**

    ```json theme={null}
    {
      "id": "Session:019542f5-b3e7-1d02-0000-000000000003",
      "accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
      "type": "EMAIL_OTP",
      "nickname": "jane@example.com",
      "createdAt": "2026-04-19T12:05:01Z",
      "updatedAt": "2026-04-19T12:05:01Z",
      "expiresAt": "2026-04-19T12:20:01Z"
    }
    ```

    The TEK public key is now the session API key. The TEK private key **is** the session signing key — the client already has it.
  </Step>

  <Step title="Client stamps the quote payload">
    The client <a href="client-keys#4-sign-a-payloadtosign">stamps the quote's `payloadToSign`</a> with the same TEK private key. Return the full Turnkey API-key stamp to your backend.
  </Step>
</Steps>

<Warning>
  Stamp the `payloadToSign` bytes exactly as Grid returned them. Do not parse, re-serialize, trim, or normalize the JSON — the stamp must cover the same bytes Grid's verifier hashes.
</Warning>

The session signing key is now valid for 15 minutes, so subsequent account actions within that window (for example, a second withdrawal) can reuse it without another `/challenge` + `/verify` round-trip.

### 8. Execute the quote

Call `/execute` with the stamp in the `Grid-Wallet-Signature` header.

```bash theme={null}
curl -X POST "$GRID_BASE_URL/quotes/Quote:019542f5-b3e7-1d02-0000-000000000006/execute" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21" \
  -H "Grid-Wallet-Signature: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzY2hlbWUiOiJTSUdOQVRVUkVfU0NIRU1FX1RLX0FQSV9QMjU2Iiwic2lnbmF0dXJlIjoiMzA0NTAyMjEwMC4uLiJ9"
```

**Response:**

```json theme={null}
{
  "id": "Quote:019542f5-b3e7-1d02-0000-000000000006",
  "status": "PROCESSING",
  "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000005",
  "totalSendingAmount": 10000000,
  "totalReceivingAmount": 975,
  "feesIncluded": 250000
}
```

The transaction is on its way. You'll receive standard transaction webhooks (`OUTGOING_PAYMENT`) as it settles — see [Transaction lifecycle](/platform-overview/core-concepts/transaction-lifecycle).

## Where to next

<CardGroup cols={2}>
  <Card title="Client keys & signing" href="./client-keys" icon="key">
    Generate the P-256 key pair, decrypt the session signing key, and sign payloads on Web, iOS, and Android.
  </Card>

  <Card title="Authentication" href="./authentication" icon="shield-check">
    OAuth and Email OTP flows, passkey reauthentication, and the full WebAuthn parameter mapping.
  </Card>

  <Card title="Sessions" href="./managing-sessions" icon="clock-rotate-left">
    List, refresh, and revoke active sessions.
  </Card>

  <Card title="Exporting a wallet" href="./exporting-wallet" icon="file-export">
    Let a customer take their wallet seed off Grid.
  </Card>
</CardGroup>
