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

# How-to: Reconcile nanopayments as a seller

> Attribute each settled nanopayment to the request that triggered it and reconcile Gateway transfer history

As a seller accepting nanopayments, Gateway settles them in batches, so a
completed request on your server confirms only that the payment was accepted,
not that it settled. Reconcile payments to attribute each settled nanopayment to
the request that triggered it, look up a payment's full state, or produce ledger
exports from Gateway's transfer history.

## Prerequisites

Before you begin, ensure that you've:

* Completed the [seller quickstart](/gateway-nanopayments/quickstarts/seller) or
  the [x402 seller integration](/gateway-nanopayments/howtos/x402-seller).
* Set up a datastore (database, key-value store, or ledger) where you can record
  payment metadata.

## Steps

<Steps>
  <Step title="Record settlement metadata">
    Register an [`onAfterSettle`](/sdks/gateway-nanopayments-sdk#lifecycle-hooks)
    hook to capture attribution data the moment settlement succeeds. If you
    added nanopayments to an existing x402 server
    ([using `x402ResourceServer`](/gateway-nanopayments/howtos/x402-seller)),
    register the equivalent hook on your `x402ResourceServer` instead. The
    payload and result fields are the same either way.

    ```ts theme={null}
    import { createGatewayMiddleware } from "@circle-fin/x402-batching/server";

    const gateway = createGatewayMiddleware({
      sellerAddress: "0xYourSellerAddress",
    });

    gateway.onAfterSettle(async (ctx) => {
      if (!ctx.result.success) return;

      const authorization = ctx.paymentPayload.payload.authorization as {
        from: string;
        to: string;
        nonce: string;
        value: string;
      };

      // db is pseudocode; replace with your datastore client
      await db.payments.insert({
        // resource.url is the URL the buyer paid for; encode a task or
        // request ID in the path (e.g. /tasks/abc123/output) for attribution
        resource: ctx.paymentPayload.resource?.url,
        // nonce is unique per payment; use it to look up the transfer later
        nonce: authorization.nonce,
        payer: ctx.result.payer,
        amount: ctx.requirements.amount,
        network: ctx.result.network,
        txHash: ctx.result.transaction,
        settledAt: new Date().toISOString(),
      });
    });
    ```

    If your attribution scheme relies on a request header the buyer sends
    rather than the resource URL, read the header from
    [`onProtectedRequest`](/sdks/gateway-nanopayments-sdk#lifecycle-hooks) and
    thread the value through to `onAfterSettle` using `AsyncLocalStorage` or a
    per-request context object.
  </Step>

  <Step title="Look up a payment by nonce">
    When you need the full transfer record for a single payment, look it up by
    nonce and then fetch by ID.

    ```ts theme={null}
    import { GatewayClient } from "@circle-fin/x402-batching/client";

    const client = new GatewayClient({
      chain: "arcTestnet",
      privateKey: process.env.PRIVATE_KEY as `0x${string}`,
    });

    async function lookupPayment(resourceUrl: string) {
      const record = await db.payments.findOne({ resource: resourceUrl });
      if (!record) throw new Error(`no payment recorded for ${resourceUrl}`);

      // nonce is unique per payment, so this returns at most one transfer
      const { transfers } = await client.searchTransfers({
        nonce: record.nonce as `0x${string}`,
      });

      const transferId = transfers[0]?.id;
      if (!transferId) return null;

      return client.getTransferById(transferId);
    }
    ```
  </Step>

  <Step title="Reconcile a date range">
    Using the `GatewayClient` from Step 2, search by recipient address and
    date range, paginate through the results, and match each transfer to a
    local record by nonce.

    ```ts theme={null}
    async function reconcile(
      sellerAddress: `0x${string}`,
      startDate: string,
    ): Promise<void> {
      let pageAfter: string | undefined = undefined;

      do {
        const page = await client.searchTransfers({
          to: sellerAddress,
          startDate,
          pageSize: 100,
          pageAfter,
        });

        for (const transfer of page.transfers) {
          const localRecord = await db.payments.findOne({
            nonce: transfer.nonce,
          });

          // Missing localRecord: payment received without attribution
          // Mismatched amount or status: flag for review
        }

        pageAfter = page.pagination?.pageAfter;
      } while (pageAfter);
    }
    ```

    Transfers with no matching local record are typically test payments or
    direct transfers sent outside the x402 flow.
  </Step>

  <Step title="Handle settlement outcomes">
    Before treating a payment as final, check `transfer.status`. Wait for
    `confirmed` before crediting the payer for accounting purposes and
    `completed` before initiating any dependent onchain operation. Transfers
    that end in `failed` were never charged. Do not fulfill the resource in
    that case.

    Register an `onSettleFailure` hook so failed settlements are recorded when
    they happen rather than surfaced later during reconciliation.

    ```ts theme={null}
    gateway.onSettleFailure(async (ctx) => {
      const authorization = ctx.paymentPayload.payload.authorization as {
        nonce: string;
      };

      await db.paymentFailures.insert({
        nonce: authorization.nonce,
        resource: ctx.paymentPayload.resource?.url,
        error: ctx.error.message,
        failedAt: new Date().toISOString(),
      });
    });
    ```

    Transfer status definitions are in the
    [SDK reference](/sdks/gateway-nanopayments-sdk#gettransferbyid-id).
  </Step>
</Steps>
