> ## Documentation Index
> Fetch the complete documentation index at: https://ramps-kph-agent-spec-amendments.mintlify.site/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.
</Tip>

## Handling webhooks

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

<Info>
  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.
</Info>

* **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.
</Tip>

<Steps>
  <Step title="Subscribe and verify signatures">
    Configure your webhook endpoint and verify signatures. See <a href="/global-p2p/platform-tools/webhooks">Webhooks</a>.

    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.
    </Note>
  </Step>

  <Step title="Process events idempotently">
    Use the envelope `id`, `data.id`, and `timestamp` to ensure idempotent handling, updating your internal ledger on each status transition.
  </Step>

  <Step title="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.
  </Step>
</Steps>

## 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.
</Note>

```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.