Lumeo Docs
Guides

Reconcile a cross-border payment

How a cross-border payout gets matched to its invoice, FIRA, and ledger entry — and what to do when auto-matching fails.

Reconciliation links three records for every cross-border payment: the payment event, the auto-generated FIRA, and the ledger entry. When all three link to the same transaction, the payment is considered reconciled and tax calculations update.

What "reconciled" means

A payment is reconciled when:

  1. It has reached CONFIRMED/COMPLETED status.
  2. A FIRA has been generated and linked to it.
  3. A balanced ledger entry exists and references both the payment and the FIRA.
  4. The payment's invoiceRef matches a known invoice in your system (if supplied).

All of steps 1–3 happen automatically as part of the compliance flow. Step 4 depends on you providing a meaningful invoiceRef when you call initiate.

Check reconciliation status

GET /api/v1/ledger/reconciliation/:paymentId
{
  "paymentId": "clx1a2b3c4d5e6f7g8h9",
  "status": "RECONCILED",
  "invoiceRef": "invoice-2026-0001",
  "firaId": "fira_01j4abc",
  "ledgerEntryId": "entry_01j4abc",
  "reconciledAt": "2026-07-01T09:14:28.000Z"
}

Subscribe to the reconciliation.completed webhook to be notified when this happens, rather than polling.

When auto-matching fails

If the invoiceRef you supplied in initiate doesn't match any known invoice, or if you omitted it entirely, the payment enters PENDING_RECONCILIATION:

{
  "paymentId": "clx_unmatched_123",
  "status": "PENDING_RECONCILIATION",
  "invoiceRef": null,
  "firaId": "fira_01j4xyz",
  "ledgerEntryId": "entry_01j4xyz",
  "reason": "No matching invoice reference found"
}

The reconciliation.pending webhook fires at this point. Tax calculations for this payment are deferred until it is resolved.

Resolving an unmatched payment

Use POST /ledger/reconciliation/:paymentId/resolve to manually link the payment to an invoice, or to flag it as a known exception (e.g. an advance payment with no invoice yet):

curl -X POST https://api.lumeo.co.in/api/v1/ledger/reconciliation/clx_unmatched_123/resolve \
  -H "Authorization: Bearer $LUMEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "invoiceRef": "invoice-2026-0007",
    "note": "Invoice was issued after payment was received"
  }'

Once resolved, the payment moves to RECONCILED and tax calculations update.

Preventing unmatched payments

The best way to avoid manual resolution is to always supply invoiceRef in the initiate call, using the same reference you use in your own invoicing system:

await lumeo.payments.initiate({
  amount: 500.0,
  currency: "USD",
  destinationAddress: recipientAddress,
  purposeCode: "P0801",
  invoiceRef: invoice.id, // your internal invoice ID
});

Use a consistent, stable format — Lumeo matches against this string exactly. Common formats: INV-2026-0001, 2026/07/001, a UUID.

Bulk unmatched report

List all payments currently in PENDING_RECONCILIATION:

GET /api/v1/ledger/reconciliation?status=PENDING_RECONCILIATION

This returns a paginated list of all unmatched payments with their FIRA IDs and ledger entry IDs, so you can batch-resolve them.

Common pitfalls

Changing your invoiceRef format mid-integration. Reconciliation matches the string exactly. If you switch from INV-2026-0001 to a UUID format partway through, older payments using the old format won't retroactively match anything new — they just stay reconciled (or unmatched) under whatever format they were created with.

Polling reconciliation status instead of subscribing to the webhook. reconciliation.completed and reconciliation.pending fire exactly once per state transition. Polling GET /ledger/reconciliation/:paymentId on a timer adds latency and burns request quota for information you'd get pushed to you for free.

On this page