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

# Reconciliation

> Reconciliation of payments

This guide explains how to reconcile transactions between your internal systems and the Grid API using two complementary mechanisms: real-time webhooks and periodic queries.

> [!TIP]
>
> Use webhooks for real-time updates and daily queries as a backstop to detect missed events or data drift.

## Handling webhooks

Listen for transaction webhooks and apply every status change to your records as it arrives.

> [!NOTE]
>
> A transaction's `status` reports where the funds are now, and it can change after settlement. A bank return moves `COMPLETED` → `FAILED` and `destination.railDetails.status` to `RETURNED`; a dishonored return moves it back to `COMPLETED`. Only `EXPIRED` is terminal. Also listen for `OUTGOING_PAYMENT.REFUND_COMPLETED` and `OUTGOING_PAYMENT.REFUND_FAILED` to track refunds on failed transactions.

* **Outbound transactions**: The originating account is debited at transaction creation. If the transaction ultimately fails, a refund is posted back to the originating account.
* **Inbound transactions**: The receiving account is credited only on success. A failure before the credit does not change balances. A return after `COMPLETED` does: the credited funds are debited back when `INCOMING_PAYMENT.FAILED` arrives with `source.railDetails.status` `RETURNED`.

> [!TIP]
>
> Grid retries failed webhooks up to 160 times over 7 days with exponential backoff. Use the dashboard to review and remediate webhook delivery issues.

**1. Subscribe and verify signatures**

Configure your webhook endpoint and verify signatures. See [Webhooks](https://agents-docs.lightspark.com/global-p2p/platform-tools/webhooks.md).

Sample webhook payload:

```json theme={null}
{
  "id": "Webhook:019542f5-b3e7-1d02-0000-0000000000ab",
  "type": "OUTGOING_PAYMENT.COMPLETED",
  "timestamp": "2025-10-03T15:30:01Z",
  "data": {
    "id": "Transaction:019542f5-b3e7-1d02-0000-000000000030",
    "status": "COMPLETED",
    "type": "OUTGOING",
    "direction": "DEBIT",
    "source": {
      "sourceType": "ACCOUNT",
      "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"
    },
    "destination": {
      "destinationType": "ACCOUNT",
      "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"
    },
    "sentAmount": {
      "amount": 10000,
      "currency": { "code": "USD", "symbol": "$", "decimals": 2 }
    },
    "receivedAmount": {
      "amount": 9200,
      "currency": { "code": "EUR", "symbol": "€", "decimals": 2 }
    },
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "platformCustomerId": "customer_12345",
    "createdAt": "2025-10-03T15:00:00Z",
    "settledAt": "2025-10-03T15:30:00Z",
    "description": "Payment for services - Invoice #1234"
  }
}
```

> [!NOTE]
>
> When funds move over an external rail, that `source` / `destination` object may include a
> `railDetails` describing the transfer. It is present for US bank rails and on-chain
> transfers today. Its `paymentRail` determines the fields: an
> `ONCHAIN` transfer carries `transactionHash` + `network` (e.g. `SOLANA`) to match on a block
> explorer, an `ACH` transfer carries its `traceNumber`, a `WIRE` carries its `imad`,
> and `RTP` / `FEDNOW` transfers carry their `endToEndId`. Rail identifiers can land shortly
> after the status changes, so the webhook payload may not include them yet; retrieve the
> transaction (`GET /transactions/{id}`) to read them once settled.

**2. Process events idempotently**

Use the envelope `id`, `data.id`, and `timestamp` to ensure idempotent handling, updating your internal ledger on each status transition.

**3. Keep settled transactions open to late events**

Treat `COMPLETED` and `FAILED` as settled, not final. An ACH entry can be returned for up to 60 days after it settles, and a return can be dishonored later still. Keep applying every webhook for a transaction, whenever it arrives, and adjust your records when its `status` changes. Use the daily query below to catch any event your endpoint missed.

## Reconcile via queries

Additionally, you can list transactions for a time window and compare with your internal records.

> [!NOTE]
>
> We recommend querying days from `00:00:00.000` to `23:59:59.999` in your preferred timezone.

```bash cURL theme={null}
curl -X GET 'https://api.lightspark.com/grid/2025-10-13/transactions?startDate=2025-10-01T00:00:00.000Z&endDate=2025-10-01T23:59:59.999Z&limit=100' \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
```

Response

```json theme={null}
{
  "data": [
    {
      "id": "Transaction:019542f5-b3e7-1d02-0000-000000000030",
      "status": "COMPLETED",
      "type": "OUTGOING",
      "direction": "DEBIT",
      "source": {
        "sourceType": "ACCOUNT",
        "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"
      },
      "destination": {
        "destinationType": "ACCOUNT",
        "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"
      },
      "sentAmount": {
        "amount": 10000,
        "currency": { "code": "USD", "symbol": "$", "decimals": 2 }
      },
      "receivedAmount": {
        "amount": 9200,
        "currency": { "code": "EUR", "symbol": "€", "decimals": 2 }
      },
      "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
      "platformCustomerId": "customer_12345",
      "description": "Payment for services - Invoice #1234",
      "exchangeRate": 0.92,
      "settledAt": "2025-10-03T15:30:00Z",
      "createdAt": "2025-10-03T15:00:00Z"
    }
  ],
  "hasMore": true,
  "nextCursor": "eyJpZCI6IlRyYW5zYWN0aW9uOjAxOTU0MmY1LWIzZTctMWQwMi0wMDAwLTAwMDAwMDAwMDAzMCJ9",
  "totalCount": 45
}
```

## Troubleshooting

* **Missing webhook**: Check delivery logs in the dashboard and ensure your endpoint returns `2xx`. Retries continue for 7 days.
* **Mismatched balances**: Re-query the date range and compare each transaction's current `status` to your records. Outbound failures are refunded. An inbound failure before the credit changes no balance; an inbound return after `COMPLETED` debits the credited funds back, and a dishonored return (`railDetails.status` `RETURN_DISHONORED`) credits them again.
* **Pagination gaps**: Always follow `nextCursor` until `hasMore` is `false`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.