Skip to main content

Documentation Index

Fetch the complete documentation index at: https://grid.lightspark.com/llms.txt

Use this file to discover all available pages before exploring further.

Understanding the transaction lifecycle helps you build robust payment flows, handle edge cases, and provide accurate status updates to your customers.

Outgoing Transaction Flow

Your customer/platform sends funds to an external recipient.
1

Create Quote

Lock in exchange rate and fees:
POST /quotes

{
  "source": {"sourceType": "ACCOUNT", "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"},
  "destination": {"destinationType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"},
  "lockedCurrencySide": "SENDING",
  "lockedCurrencyAmount": 100000
}
Response:
  • Quote ID
  • Locked exchange rate
  • Expiration time (typically ~5 minutes or greater, depending on corridor)
2

Execute Quote

Initiate the payment:
POST /quotes/{quoteId}/execute
Result:
  • Transaction created with status PENDING
  • Source account debited immediately
  • OUTGOING_PAYMENT.PENDING webhook sent
3

Processing

Grid handles:
  • Currency conversion (if applicable)
  • Routing to appropriate payment rail
  • Settlement with destination bank/wallet
Status: PROCESSING
4

Completion or Failure

Success Path:
  • Funds delivered to recipient
  • Status: COMPLETED
  • settledAt timestamp populated
  • OUTGOING_PAYMENT.COMPLETED webhook sent
Failure Path:
  • Delivery failed (invalid account, etc.)
  • Status: FAILED
  • failureReason populated
  • OUTGOING_PAYMENT.FAILED webhook sent
  • Refund initiated automatically — track via the refund object and OUTGOING_PAYMENT.REFUND_* webhooks
Most transactions on Grid are completed in seconds.

Same-Currency Transfers

For same-currency transfers without quotes:

Transfer-Out (Internal → External)

POST /transfer-out

{
  "source": {"accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"},
  "destination": {"accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123", "currency": "USD"},
  "amount": 100000
}
Response:
{
  "id": "Transaction:...",
  "status": "PENDING",
  "type": "OUTGOING"
}
Follows same lifecycle as quote-based outgoing transactions.

Transfer-In (External → Internal)

POST /transfer-in

{
  "source": {"accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123", "currency": "USD"},
  "destination": {"accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"},
  "amount": 100000
}
Only works for “pullable” external accounts (e.g., debit cards).

Outgoing Payment Status

A single status field represents whether the transaction reached its destination:
StatusDescription
PENDINGQuote is pending confirmation
EXPIREDQuote wasn’t executed before the expiry window
PROCESSINGExecuting the quote after receiving funds (checking internal balances, or push/pull to/from external account)
COMPLETEDPayout successfully reached the destination account
FAILEDSomething went wrong — accompanied by a failureReason
EXPIRED and FAILED are terminal states, but COMPLETED is not always final — a bank can return a payment after it was marked COMPLETED, moving it to FAILED. Always continue processing webhook events for transactions even after they reach COMPLETED.

State Diagram

Refund Object

When a payment fails or is cancelled, refunds are tracked in a dedicated object on the transaction, decoupled from the payment status:
FieldDescription
referenceRefund reference ID
initiatedAtTimestamp when refund was initiated
settledAtTimestamp when refund settled
statusPENDING, COMPLETED, or FAILED
reasonWhy the refund occurred — TRANSACTION_FAILED, USER_CANCELLATION, or TIMEOUT
{
  "id": "Transaction:019542f5-b3e7-1d02-0000-000000000030",
  "status": "FAILED",
  "type": "OUTGOING",
  "failureReason": "QUOTE_EXECUTION_FAILED",
  "refund": {
    "reference": "UMA-Q12345-REFUND",
    "initiatedAt": "2025-10-03T15:10:00Z",
    "settledAt": "2025-10-03T15:15:00Z",
    "status": "COMPLETED",
    "reason": "TRANSACTION_FAILED"
  }
}

Webhooks

Outgoing payment webhooks use the format OUTGOING_PAYMENT.<STATUS>. The webhook request body contains the full transaction resource.

Event Types

EventDescription
OUTGOING_PAYMENT.PENDINGTransaction created, quote pending confirmation
OUTGOING_PAYMENT.PROCESSINGQuote confirmed, payout in progress
OUTGOING_PAYMENT.COMPLETEDPayout reached destination
OUTGOING_PAYMENT.FAILEDPayout failed
OUTGOING_PAYMENT.EXPIREDQuote expired before execution
OUTGOING_PAYMENT.REFUND_PENDINGRefund initiated
OUTGOING_PAYMENT.REFUND_COMPLETEDRefund settled
OUTGOING_PAYMENT.REFUND_FAILEDRefund failed

Example Payloads

{
  "type": "OUTGOING_PAYMENT.PENDING",
  "data": {
    "id": "Transaction:...",
    "status": "PENDING",
    "type": "OUTGOING",
    "sentAmount": {"amount": 100000, "currency": {"code": "USD"}},
    "receivedAmount": {"amount": 92000, "currency": {"code": "EUR"}},
    "createdAt": "2025-10-03T15:00:00Z"
  }
}

Handling Webhooks

app.post('/webhooks/grid', async (req, res) => {
  const { data, type } = req.body;

  switch (type) {
    case 'OUTGOING_PAYMENT.COMPLETED':
      await notifyCustomer(data.customerId, 'Payment delivered!');
      break;

    case 'OUTGOING_PAYMENT.FAILED':
      await notifyCustomer(data.customerId, `Payment failed: ${data.failureReason}`);
      break;

    case 'OUTGOING_PAYMENT.REFUND_COMPLETED':
      await notifyCustomer(data.customerId, 'Refund completed.');
      break;

    case 'OUTGOING_PAYMENT.REFUND_FAILED':
      await notifyCustomer(data.customerId, 'Refund failed. Contact support.');
      break;
  }

  await updateTransactionStatus(data.id, type);
  res.status(200).json({ received: true });
});

Scenarios

The standard successful payment flow:
  1. OUTGOING_PAYMENT.PENDING
  2. OUTGOING_PAYMENT.PROCESSING
  3. OUTGOING_PAYMENT.COMPLETED

Listing Transactions

Query all transactions for a customer or date range:
GET /transactions?customerId=Customer:abc123&startDate=2025-10-01T00:00:00Z&limit=50
Response:
{
  "data": [
    {
      "id": "Transaction:...",
      "status": "COMPLETED",
      "type": "OUTGOING",
      "sentAmount": {"amount": 100000, "currency": {"code": "USD"}},
      "receivedAmount": {"amount": 92000, "currency": {"code": "EUR"}},
      "settledAt": "2025-10-03T15:05:00Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
Use for reconciliation and reporting.

Failure Handling

Common Failure Reasons

Failure ReasonDescriptionRecovery
QUOTE_EXPIREDQuote expired before executionCreate new quote
QUOTE_EXECUTION_FAILEDError executing the quoteCreate new quote
INSUFFICIENT_BALANCESource account lacks fundsFund account, retry
LIGHTNING_PAYMENT_FAILEDLightning network payment could not be routedRetry or use alternative rail
FUNDING_AMOUNT_MISMATCHFunding amount doesn’t match expected amountVerify amounts and retry
COUNTERPARTY_POST_TX_FAILEDPost-transaction processing at counterparty failedContact support
When a transaction fails, a refund is initiated automatically. Track the refund via the refund object on the transaction and OUTGOING_PAYMENT.REFUND_* webhook events. See Refund Object above.

Best Practices

Save transaction IDs to your database:
const transaction = await executeQuote(quoteId);
await db.transactions.insert({
  gridTransactionId: transaction.id,
  internalPaymentId: paymentId,
  status: transaction.status,
  createdAt: new Date()
});
Use idempotency keys for safe retries:
const idempotencyKey = `payment-${userId}-${Date.now()}`;
await createQuote({...params, idempotencyKey});
Translate technical statuses to user-friendly messages:
function getUserMessage(webhookType, data) {
  switch (webhookType) {
    case 'OUTGOING_PAYMENT.PENDING':
      return 'Payment processing...';
    case 'OUTGOING_PAYMENT.PROCESSING':
      return 'Payment in progress...';
    case 'OUTGOING_PAYMENT.COMPLETED':
      return 'Payment delivered!';
    case 'OUTGOING_PAYMENT.FAILED':
      return 'Payment failed. Please try again or contact support.';
    case 'OUTGOING_PAYMENT.REFUND_PENDING':
      return 'Refund in progress...';
    case 'OUTGOING_PAYMENT.REFUND_COMPLETED':
      return 'Refund completed. Funds returned to your account.';
    case 'OUTGOING_PAYMENT.REFUND_FAILED':
      return 'Refund failed. Please contact support.';
    default:
      return 'Payment status updated.';
  }
}