Skip to main content
Rewards quickstart hero This guide walks you through the complete process of buying Bitcoin with USD and sending it to a self-custody Spark wallet, from customer onboarding through quote execution.

Understanding Entity Mapping for Rewards

In this guide, the entities map as follows (platform-funded model):
Entity TypeWho They AreIn This Example
PlatformYour rewards app paying rewards directlyYour cashback/rewards platform
Customer(Not used in this model)N/A
External AccountUsers’ crypto wallets receiving rewardsUser’s self-custody Spark wallet
Flow: Your platform funds its internal account → sends Bitcoin micro-payouts directly → to users’ external crypto wallets at scale. This is common for cashback apps where you earn affiliate commissions and share them with users.
For white-label reward programs where brands like Nike or Starbucks fund their own reward campaigns, those brands would be created as Customers who manage their own reward budgets.

Prerequisites

Before starting this guide, ensure you have:
  • A Grid API account with valid authentication credentials
  • Access to the Grid API endpoints (production or sandbox)
  • A webhook endpoint configured to receive notifications
  • A Spark wallet address where the Bitcoin will be sent

Overview

The process consists of the following steps:
  1. List platform internal accounts to find your platform’s USD funding instructions
  2. Fund your internal account via ACH push and receive a webhook notification
  3. Generate a spark wallet for your customer, or let them connect their own
  4. Register the destination wallet as an external account
  5. Execute a quote to complete the Bitcoin purchase and transfer a reward to the user’s own Spark wallet.

Step 1: List your platform’s internal accounts

When your platform is first created, it is automatically assigned an internal account with a balance in your configured fiat currency. List your platform’s internal accounts to see the available balances and funding instructions for USD.

Request

curl -X GET "https://api.lightspark.com/grid/2025-10-13/platform/internal-accounts?currency=USD" \
  -H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET"

Response

{
  "data": [
    {
      "id": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965",
      "balance": {
        "amount": 0,
        "currency": {
          "code": "USD",
          "name": "United States Dollar",
          "symbol": "$",
          "decimals": 2
        }
      },
      "fundingPaymentInstructions": [
        {
          "instructionsNotes": "Include the reference code in your ACH transfer memo",
          "accountOrWalletInfo": {
            "accountType": "US_ACCOUNT",
            "reference": "FUND-BTC123",
            "accountNumber": "9876543210",
            "routingNumber": "021000021",
            "accountHolderName": "Lightspark Payments FBO John Doe",
            "bankName": "JP Morgan Chase"
          }
        },
        { 
          "accountOrWalletInfo": {
            "accountType": "SPARK_WALLET",
            "assetType": "USDB",
            "address": "spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu",
            "invoice": "lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs"
          }
        },
        {
          "accountOrWalletInfo": {
            "accountType": "SOLANA_WALLET",
            "assetType": "USDC",
            "address": "4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg"
          }
        }
      ],
      "createdAt": "2025-10-03T12:00:00Z",
      "updatedAt": "2025-10-03T12:00:00Z"
    }
  ]
}
The fundingPaymentInstructions provide the bank account details and reference code needed to fund this internal account via ACH pull from the customer’s bank.
You can also see that there are Spark wallet funding instructions in this example response which can be used to fund the internal account with USDB instantly.

Step 2: Fund your Internal Account

You can initiate an ACH transfer from your bank to the account details provided in the funding instructions, making sure to include the reference code FUND-BTC123 in the transfer memo.
In sandbox mode, you can use the /sandbox/internal-accounts/{accountId}/fund endpoint to simulate receiving funds. In production, actual ACH transfers typically take 1-3 business days to settle.

Webhook Notification

When the funds are received and the internal account balance is updated, you’ll receive a webhook notification:
{
  "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965",
  "oldBalance": {
    "amount": 0,
    "currency": {
      "code": "USD",
      "name": "United States Dollar",
      "symbol": "$",
      "decimals": 2
    }
  },
  "newBalance": {
    "amount": 200000,
    "currency": {
      "code": "USD",
      "name": "United States Dollar",
      "symbol": "$",
      "decimals": 2
    }
  },
  "timestamp": "2025-10-03T14:32:00Z",
  "webhookId": "Webhook:019542f5-b3e7-1d02-0000-000000000020",
  "type": "ACCOUNT_STATUS"
}
The internal account now has a balance of $2,000.00 (200000 cents). You can use this balance to instantly distribute Bitcoin rewards to your customers.

Step 3: Customer Onboarding

This guide assumes you have a Spark wallet address for your customer who will receive the Bitcoin reward. For rewards, the only entity who needs to be KYB’d is the entity paying for the reward - in this case, you, the platform! All you need in order to pay out a reward is the wallet address. No need to go through the full hosted KYC flow for this use case! To generate a spark wallet, you can use a tool like Privy or the Spark SDK directly.

Step 4: Register the Destination Spark Wallet

Register the customer’s Spark wallet as an external account.

Request

curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers/external-accounts" \
  -H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "currency": "BTC",
    "accountInfo": {
      "accountType": "SPARK_WALLET",
      "address": "spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu"
    }
  }'

Response

{
  "id": "ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456",
  "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
  "status": "ACTIVE",
  "currency": "BTC",
  "accountInfo": {
    "accountType": "SPARK_WALLET",
    "address": "spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu"
  }
}

Step 5: Create and Execute a Quote to the Customer’s Spark Wallet

Create and execute a trade from USD to BTC using the external account as the destination.

Request

curl -X POST "https://api.lightspark.com/grid/2025-10-13/quotes" \
  -H "Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"
    },
    "destination": {
      "destinationType": "ACCOUNT",
      "accountId": "ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456"
    },
    "lockedCurrencySide": "SENDING",
    "lockedCurrencyAmount": 100,
    "immediatelyExecute": true,
    "description": "Bitcoin reward payout!"
  }'
Immediate Quote Execution (Market Order): Note that immediatelyExecute is set to true in this example. Because we always just want to send $1.00 worth of BTC to users as a reward at the current market rate, we don’t need to lock a quote and view the rate details before executing. If you want to lock a quote and confirm fees and exchange rate details before executing the quote, set immediatelyExecute to false or omit the field.

Response

{
  "quoteId": "Quote:019542f5-b3e7-1d02-0000-000000000020",
  "status": "PROCESSING",
  "createdAt": "2025-10-03T15:00:00Z",
  "expiresAt": "2025-10-03T15:05:00Z",
  "source": {
    "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965",
    "currency": "USD"
  },
  "destination": {
    "accountId": "ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456",
    "currency": "BTC"
  },
  "sendingCurrency": {
    "code": "USD",
    "name": "United States Dollar",
    "symbol": "$",
    "decimals": 2
  },
  "receivingCurrency": {
    "code": "BTC",
    "name": "Bitcoin",
    "symbol": "₿",
    "decimals": 8
  },
  "totalSendingAmount": 100,
  "totalReceivingAmount": 810,
  "exchangeRate": 8.1,
  "feesIncluded": 5,
  "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000025"
}
The quote shows:
  • Sending: $1.00 USD (including $0.05 fee)
  • Receiving: 0.0000081 BTC (810 satoshis)
  • Exchange rate: 8.1 sats per USD cent (~$123,000 per BTC) The quote status changes to PROCESSING and the Bitcoin transfer is initiated. USD is debited from the internal account, Bitcoin is purchased, and then sent to the Spark wallet address.
You can track the status by:
  1. Polling the quote endpoint: GET /quotes/{quoteId}
  2. Waiting for a webhook notification

Completion Webhook

When the Bitcoin transfer completes, you’ll receive a webhook notification:
{
  "transaction": {
    "id": "Transaction:019542f5-b3e7-1d02-0000-000000000025",
    "status": "COMPLETED",
    "type": "OUTGOING",
    "sentAmount": {
      "amount": 100,
      "currency": {
        "code": "USD",
        "name": "United States Dollar",
        "symbol": "$",
        "decimals": 2
      }
    },
    "receivedAmount": {
      "amount": 810,
      "currency": {
        "code": "BTC",
        "name": "Bitcoin",
        "symbol": "₿",
        "decimals": 8
      }
    },
    "settledAt": "2025-10-03T15:01:45Z",
    "createdAt": "2025-10-03T15:00:00Z",
    "description": "Bitcoin purchase for self-custody",
    "exchangeRate": 8.1,
    "quoteId": "Quote:019542f5-b3e7-1d02-0000-000000000020"
  },
  "timestamp": "2025-10-03T15:02:00Z",
  "webhookId": "Webhook:019542f5-b3e7-1d02-0000-000000000030",
  "type": "OUTGOING_PAYMENT"
}
Bitcoin transfers to Spark wallets typically complete within seconds, much faster than traditional Bitcoin on-chain transactions.

Summary

You’ve successfully completed a Bitcoin purchase and transfer to a self-custody Spark wallet! Here’s what happened:
  1. ✅ Listed internal accounts and obtained USD funding instructions
  2. ✅ Funded the internal account with USD via ACH
  3. ✅ Generated a Spark wallet for the customer
  4. ✅ Registered the destination Spark wallet as an external account
  5. ✅ Created and executed a quote to purchase Bitcoin and send to the Spark wallet
The customer now has 810 Satoshis in their self-custody Spark wallet!

Next Steps

  • Transaction history: Use GET /transactions to track all Bitcoin purchases
  • Price monitoring: Build price alerts using the lookup endpoint to monitor rates
  • Webhook verification: Implement signature verification for webhook security (see Webhooks guide)