Skip to main content
Internal accounts are Lightspark managed accounts that hold funds within the Grid platform. They allow you to receive deposits and send payments to external bank accounts or other payment destinations. They are useful for holding funds on behalf or the platform or customers which will be used for instant, 24/7 quotes and transfers out of the system. Internal accounts are created for both:
  • Platform-level accounts: Hold pooled funds for your platform operations (rewards distribution, reconciliation, etc.)
  • Customer accounts: Hold individual customer funds for their transactions
Internal accounts are automatically created when you onboard a customer, based on your platform’s currency configuration. Platform-level internal accounts are created when you configure your platform with supported currencies.

How internal accounts work

Internal accounts act as an intermediary holding account in the payment flow:
  1. Deposit funds: You or your customers deposit money into internal accounts using bank transfers (ACH, wire, PIX, etc.) or crypto transfers
  2. Hold balance: Funds are held securely in the internal account until needed
  3. Send payments: You initiate transfers from internal accounts to external destinations
Each internal account:
  • Is denominated in a single currency (USD, EUR, etc.)
  • Has a unique balance that you can query at any time
  • Includes unique payment instructions for depositing funds
  • Supports multiple funding methods depending on the currency

Retrieving internal accounts

List customer internal accounts

To retrieve all internal accounts for a specific customer, use the customer ID to filter the results:
Request internal accounts for a customer
curl -X GET 'https://api.lightspark.com/grid/2025-10-13/customers/internal-accounts?customerId=Customer:019542f5-b3e7-1d02-0000-000000000001' \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
{
  "data": [
    {
      "id": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965",
      "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
      "balance": {
        "amount": 50000,
        "currency": {
          "code": "USD",
          "name": "United States Dollar",
          "symbol": "$",
          "decimals": 2
        }
      },
      "fundingPaymentInstructions": [
        {
          "reference": "FUND-ABC123",
          "instructionsNotes": "Include the reference code in your ACH transfer memo",
          "bankAccountInfo": {
            "accountType": "US_ACCOUNT",
            "accountNumber": "9876543210",
            "routingNumber": "021000021",
            "accountHolderName": "Lightspark Payments FBO John Doe",
            "bankName": "JP Morgan Chase"
          }
        }
      ],
      "createdAt": "2025-10-03T12:00:00Z",
      "updatedAt": "2025-10-03T14:30:00Z"
    }
  ],
  "hasMore": false,
  "totalCount": 1
}

Filter by currency

You can filter internal accounts by currency to find accounts for specific denominations:
curl -X GET 'https://api.lightspark.com/grid/2025-10-13/customers/internal-accounts?customerId=Customer:019542f5-b3e7-1d02-0000-000000000001&currency=USD' \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"

List platform internal accounts

To retrieve platform-level internal accounts (not tied to individual customers), use the platform internal accounts endpoint:
curl -X GET 'https://api.lightspark.com/grid/2025-10-13/platform/internal-accounts' \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
Platform internal accounts are useful for managing pooled funds, distributing rewards, or handling platform-level operations.

Understanding funding payment instructions

Each internal account includes fundingPaymentInstructions that tell your customers how to deposit funds. The structure varies by payment rail and currency:
For USD accounts, instructions include routing and account numbers:
{
  "reference": "FUND-ABC123",
  "instructionsNotes": "Include the reference code in your ACH transfer memo",
  "bankAccountInfo": {
    "accountType": "US_ACCOUNT",
    "accountNumber": "9876543210",
    "routingNumber": "021000021",
    "accountHolderName": "Lightspark Payments FBO John Doe",
    "bankName": "JP Morgan Chase"
  }
}
Each internal account has unique banking details in the bankAccountInfo field, which ensures deposits are automatically credited to the correct account.
For EUR accounts, instructions use SEPA IBAN numbers:
{
  "reference": "FUND-EUR789",
  "instructionsNotes": "Include reference in SEPA transfer description",
  "bankAccountInfo": {
    "accountType": "IBAN",
    "iban": "DE89370400440532013000",
    "swiftBic": "DEUTDEFF",
    "accountHolderName": "Lightspark Payments FBO Maria Garcia",
    "bankName": "Banco de México"
  }
}
For stablecoin accounts, instructions use Spark wallet addresses:
{
  "invoice": "lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs",
  "instructionsNotes": "Use the invoice when making Spark payment",
  "bankAccountInfo": {
    "accountType": "SPARK_WALLET",
    "asset": "USDB",
    "address": "spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu"
  }
}

Checking account balances

The internal account balance reflects all deposits and withdrawals. The balance includes:
  • amount: The balance amount in the smallest currency unit (cents for USD, centavos for MXN/BRL, etc.)
  • currency: Full currency details including code, name, symbol, and decimal places

Example balance check

Fetch the balance of an internal account
curl -X GET 'https://api.lightspark.com/grid/2025-10-13/customers/internal-accounts/InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965' \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
{
  "data": {
    "id": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965",
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "balance": {
      "amount": 50000,
      "currency": {
        "code": "USD",
        "name": "United States Dollar",
        "symbol": "$",
        "decimals": 2
      }
    }
  }
}
Always check the decimals field in the currency object to correctly convert between display amounts and API amounts. For example, USD has 2 decimals, so an amount of 50000 represents $500.00.

Displaying funding instructions to customers

When customers need to deposit funds themselves, display the funding payment instructions in your application:
1

Retrieve the internal account

Fetch the customer’s internal account for their desired currency using the API.
2

Extract payment instructions

Parse the fundingPaymentInstructions array and select the appropriate instructions based on your customer’s preferred payment method.
const instructions = account.fundingPaymentInstructions[0];
const bankInfo = instructions.bankAccountInfo;
3

Display instructions clearly

Show the payment details prominently in your UI:
  • Account holder name
  • Bank name and routing information (account/routing number, CLABE, PIX key, etc.)
  • Reference code (if provided)
  • Any additional notes from instructionsNotes
The unique banking details in each internal account automatically route deposits to the correct destination.
4

Monitor for deposits

Set up webhook listeners to receive notifications when deposits are credited to the internal account. The account balance will update automatically.
You’ll receive ACCOUNT_STATUS webhook events when the internal account balance changes.

Best practices

Ensure your customers have all the information needed to make deposits. Consider implementing:
  • Clear display of all banking details from fundingPaymentInstructions
  • Copy-to-clipboard functionality for account numbers and reference codes
  • Email/SMS confirmations with complete deposit instructions
Set up monitoring to alert customers when their balance is low:
if (account.balance.amount < minimumThreshold) {
  await notifyCustomer({
    type: 'LOW_BALANCE',
    account: account.id,
    instructions: account.fundingPaymentInstructions
  });
}
If your platform supports multiple currencies, organize internal accounts by currency in your UI:
const accountsByCurrency = accounts.data.reduce((acc, account) => {
  const code = account.balance.currency.code;
  acc[code] = account;
  return acc;
}, {});

// Quick lookup: accountsByCurrency['USD']

Internal account details (especially funding instructions) rarely change, so you can cache them safely. However, always fetch fresh balance data before initiating transfers.

Using internal accounts for ramps

Internal accounts play a critical role in both on-ramp and off-ramp flows:

For on-ramps (Fiat → Crypto)

Internal accounts are optional for on-ramp flows using just-in-time (JIT) funding:
  • JIT funding: Quotes provide payment instructions directly; funds flow through without requiring an internal account
  • Pre-funded model: Deposit fiat to internal account first, then create and execute conversion quotes
Most on-ramp implementations use JIT funding to avoid holding customer balances and simplify compliance.

For off-ramps (Crypto → Fiat)

Internal accounts are essential for off-ramp flows:
  1. Deposit crypto: Transfer Bitcoin or stablecoins to the internal account
  2. Hold balance: Crypto balance is held securely until conversion
  3. Execute conversion: Create and execute quote to convert crypto to fiat and send to bank account
Off-ramps require pre-funded internal accounts with crypto balances. Grid supports Fiat, BTC, and Stablecoin deposits.

Crypto funding for internal accounts

For off-ramp operations, fund internal accounts with cryptocurrency:

Bitcoin (Lightning Network)

Internal accounts can receive Bitcoin via Lightning Network:
{
  "fundingPaymentInstructions": [
    {
      "accountType": "SPARK_WALLET",
      "asset": "BTC",
      "address": "spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu",
      "invoice": "lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3s..."
    }
  ]
}
Deposit methods:
  • Spark address: Reusable address for multiple deposits
  • Lightning invoice: Single-use invoice for specific amounts
Lightning Network transfers are typically instant and have minimal fees, making them ideal for crypto deposits.

Stablecoins

Internal accounts can also receive stablecoins via Lightning Network or Spark:
{
  "fundingPaymentInstructions": [
    {
      "accountType": "SPARK_WALLET",
      "asset": "USDB",
      "address": "spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu"
    }
  ]
}
Stablecoins like USDB are 1:1 pegged to USD, offering price stability for off-ramp balances while maintaining instant settlement via Lightning Network.

Multi-currency balances

Internal accounts support both fiat and crypto balances:

Example: Account with USD and BTC

{
  "data": [
    {
      "id": "InternalAccount:usd123",
      "customerId": "Customer:cust001",
      "balance": {
        "amount": 100000,
        "currency": {
          "code": "USD",
          "name": "United States Dollar",
          "symbol": "$",
          "decimals": 2
        }
      }
    },
    {
      "id": "InternalAccount:btc456",
      "customerId": "Customer:cust001",
      "balance": {
        "amount": 10000000,
        "currency": {
          "code": "BTC",
          "name": "Bitcoin",
          "symbol": "₿",
          "decimals": 8
        }
      }
    }
  ]
}
Always check the decimals field when working with balances. USD uses 2 decimals (cents), while BTC uses 8 decimals (satoshis).

Monitoring account balance changes

Subscribe to ACCOUNT_STATUS webhooks to receive real-time balance updates:
{
  "accountId": "InternalAccount:btc456",
  "oldBalance": {
    "amount": 5000000,
    "currency": { "code": "BTC", "decimals": 8 }
  },
  "newBalance": {
    "amount": 10000000,
    "currency": { "code": "BTC", "decimals": 8 }
  },
  "timestamp": "2025-10-03T14:32:00Z",
  "webhookId": "Webhook:webhook001",
  "type": "ACCOUNT_STATUS"
}
Balance updates are triggered by: - Incoming deposits (fiat or crypto) - Quote executions (funds debited) - Failed transaction reversals (funds credited back)

Ramp-specific use cases

Pre-funding for off-ramps

Maintain crypto balances for instant fiat conversions:
// Check Bitcoin balance before off-ramp
const accounts = await getInternalAccounts(customerId);
const btcAccount = accounts.data.find(
  (acc) => acc.balance.currency.code === "BTC"
);

if (btcAccount.balance.amount >= requiredSats) {
  // Create quote to convert BTC to fiat
  const quote = await createQuote({
    source: { accountId: btcAccount.id },
    destination: { accountId: bankAccountId, currency: "USD" },
    lockedCurrencySide: "SENDING",
    lockedCurrencyAmount: requiredSats,
  });

  // Execute conversion
  await executeQuote(quote.id);
}

Platform treasury management

Use platform-level internal accounts for pooled liquidity:
# Get platform internal accounts
curl -X GET 'https://api.lightspark.com/grid/2025-10-13/platform/internal-accounts?currency=BTC' \
  -H 'Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET'
Platform internal accounts enable centralized treasury management for high-volume ramp operations.

Best practices for ramps

  • On-ramps: Use JIT funding to avoid holding customer fiat
  • Off-ramps: Pre-fund with crypto for instant fiat conversions
  • High volume: Consider platform-level accounts for pooled liquidity
// Track BTC value in USD
const btcBalance = account.balance.amount; // satoshis
const currentRate = await getBtcUsdRate();
const usdValue = (btcBalance / 100000000) * currentRate;

if (usdValue > maxExposure) {
// Convert excess BTC to stablecoin
await convertToStablecoin(btcBalance - targetBalance);
}

Set up proactive notifications for low balances:
// Alert when off-ramp liquidity is low
if (btcAccount.balance.amount < minimumSats) {
  await alertTreasury({
    type: 'LOW_CRYPTO_BALANCE',
    account: btcAccount.id,
    balance: btcAccount.balance.amount,
    minimumRequired: minimumSats
  });
}

Next steps

I