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:
- It has reached
CONFIRMED/COMPLETEDstatus. - A FIRA has been generated and linked to it.
- A balanced ledger entry exists and references both the payment and the FIRA.
- The payment's
invoiceRefmatches 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_RECONCILIATIONThis 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.