Overview
Grid enables seamless conversion between fiat currencies and cryptocurrencies via the Lightning Network. Use quotes to lock exchange rates and get payment instructions for completing transfers.
On-ramp (Fiat → Crypto): User sends fiat → Grid detects payment → Crypto sent to wallet
Off-ramp (Crypto → Fiat): Execute quote → Grid processes crypto → Fiat sent to bank
Prerequisites
Customer created in Grid
On-ramps: Destination crypto wallet (Spark address) + webhook endpoint
Off-ramps: Internal account with crypto + external bank account registered
On-ramp: Fiat to crypto
Create a quote
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": {
"customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
"currency": "USD"
},
"destination": {
"externalAccountDetails": {
"customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
"currency": "BTC",
"beneficiary": {
"counterPartyType": "INDIVIDUAL",
"fullName": "John Doe",
"email": "john@example.com"
},
"accountInfo": {
"accountType": "SPARK_WALLET",
"address": "spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu"
}
}
},
"lockedCurrencySide": "RECEIVING",
"lockedCurrencyAmount": 100000,
"description": "Buy 0.001 BTC"
}'
Response includes payment instructions:
{
"id" : "Quote:019542f5-b3e7-1d02-0000-000000000006" ,
"totalSendingAmount" : 6500 ,
"receivingAmount" : 100000 ,
"expiresAt" : "2025-10-03T12:05:00Z" ,
"paymentInstructions" : [
{
"accountOrWalletInfo" : {
"accountType" : "US_ACCOUNT" ,
"reference" : "UMA-Q12345-REF" ,
"accountNumber" : "1234567890" ,
"routingNumber" : "021000021" ,
"bankName" : "Grid Settlement Bank"
}
},
{
"accountOrWalletInfo" : {
"accountType" : "SOLANA_WALLET" ,
"assetType" : "USDC" ,
"address" : "4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg"
}
}
]
}
Display payment instructions
function displayPaymentInstructions ( quote ) {
const instructions = quote . paymentInstructions [ 0 ];
return {
amount: `$ ${ ( quote . totalSendingAmount / 100 ). toFixed ( 2 ) } ` ,
bankName: instructions . accountOrWalletInfo . bankName ,
accountNumber: instructions . accountOrWalletInfo . accountNumber ,
routingNumber: instructions . accountOrWalletInfo . routingNumber ,
referenceCode: instructions . reference , // User must include this
expiresAt: quote . expiresAt ,
willReceive: ` ${ quote . receivingAmount / 100000000 } BTC` ,
};
}
Monitor completion
Grid sends a webhook when the transfer completes:
app . post ( "/webhooks/grid" , async ( req , res ) => {
const { type , transaction } = req . body ;
if ( type === "OUTGOING_PAYMENT" && transaction . status === "COMPLETED" ) {
await notifyUser ( transaction . customerId , {
message: "Your Bitcoin purchase is complete!" ,
amount: ` ${ transaction . receivedAmount . amount / 100000000 } BTC` ,
});
}
res . status ( 200 ). json ({ received: true });
});
Off-ramp: Crypto to fiat
Create and execute a quote
// 1. Create quote
const quote = await fetch ( "https://api.lightspark.com/grid/2025-10-13/quotes" , {
method: "POST" ,
body: JSON . stringify ({
source: {
accountId: "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" ,
},
destination: {
accountId: "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965" ,
currency: "USD" ,
},
lockedCurrencySide: "SENDING" ,
lockedCurrencyAmount: 100000 , // 0.001 BTC
description: "Sell 0.001 BTC" ,
}),
}). then (( r ) => r . json ());
// 2. Execute quote
const result = await fetch (
`https://api.lightspark.com/grid/2025-10-13/quotes/ ${ quote . id } /execute` ,
{
method: "POST" ,
headers: { Authorization: `Basic ${ credentials } ` },
}
). then (( r ) => r . json ());
Track completion
app . post ( "/webhooks/grid" , async ( req , res ) => {
const { type , transaction } = req . body ;
if ( type === "OUTGOING_PAYMENT" && transaction . status === "COMPLETED" ) {
await notifyUser ( transaction . customerId , {
message: "Your USD withdrawal is complete!" ,
amount: `$ ${ transaction . receivedAmount . amount / 100 } ` ,
});
}
res . status ( 200 ). json ({ received: true });
});
For instant on-ramps (e.g., reward payouts), use immediatelyExecute: true with an internal account source:
const quote = await fetch ( "https://api.lightspark.com/grid/2025-10-13/quotes" , {
method: "POST" ,
body: JSON . stringify ({
source: {
sourceType: "ACCOUNT" ,
accountId: "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123" ,
},
destination: {
destinationType: "EXTERNAL_ACCOUNT_DETAILS" ,
externalAccountDetails: {
customerId: "Customer:..." ,
currency: "BTC" ,
accountInfo: {
accountType: "SPARK_WALLET" ,
address: "spark1..." ,
},
},
},
lockedCurrencySide: "RECEIVING" ,
lockedCurrencyAmount: 100000 ,
immediatelyExecute: true ,
}),
}). then (( r ) => r . json ());
immediatelyExecute can only be used with sources that are either internal accounts or external accounts with direct pull functionality (e.g., ACH pull).
Best practices
async function refreshQuoteIfNeeded ( quote ) {
const expiresAt = new Date ( quote . expiresAt );
const now = new Date ();
if ( expiresAt - now < 60000 ) {
// Less than 1 minute left
return await createNewQuote ( quote . originalParams );
}
return quote ;
}
Settlement times by payment rail
const settlementTimes = {
US_ACCOUNT: "1-3 business days (ACH)" ,
WIRE: "Same day" ,
SPARK_WALLET: "Instant" ,
PIX: "Instant" ,
SEPA: "1-2 business days" ,
};
Handle failed transactions
if ( transaction . status === "FAILED" ) {
await notifyUser ( transaction . customerId , {
message: "Transaction failed" ,
reason: transaction . failureReason ,
action: "retry" ,
});
if ( transaction . failureReason === "QUOTE_EXPIRED" ) {
await createNewQuote ( transaction . originalParams );
}
}
Next steps