Skip to main content
ReconLayer reconciles payments that move across bank rails, stablecoins, and payout providers. At its core, the model answers one question per payment: did what was supposed to happen actually happen, and if not, why? This page walks through the full lifecycle of a payment in ReconLayer, end to end, using the actual object names and field names you’ll see in the API.

The two sides of every payment

ReconLayer keeps two things separate and compares them:
  1. What should happen β€” a PaymentIntent. A declaration, usually made via your own system, of an expected payment: amounts, currencies, beneficiary, and corridor.
  2. What actually happened β€” evidence, arriving as RawRecords from webhooks, file imports, or the evidence API, normalized into FlowLegs (real movements of money).
The reconciliation engine compares these two and produces a ReconciliationCase: one row per PaymentIntent, holding the verdict, any exception, and a full breakdown of fees, FX, and rounding that explain (or fail to explain) the difference between expected and actual amounts.
For precise field-by-field definitions of every object mentioned on this page, see Core Concepts.

Two ways to start: expectation-first vs. evidence-first

ReconLayer supports two valid entry points into the model:
You (the client) know about the payment before ReconLayer sees any evidence of it. Typical sources:
  • A direct call to POST /v1/payment-intents when you initiate or observe a payment on your side.
  • A client_internal_ledger file import β€” each valid row creates a PaymentIntent.
ReconLayer creates the PaymentIntent and, in the same transaction, an open ReconciliationCase with reconciliationStatus: 'unreconciled'. The case then waits for evidence.
Both paths converge on the same model. Reconciliation happens whenever the system has both enough expectation and enough evidence to compare them β€” regardless of which arrived first.

Step by step: a cross-border payout

Here’s a concrete walkthrough combining both entry points, following a single externalReference.
1

Declare the expected payment

Your system calls POST /v1/payment-intents:
{
  "externalReference": "ACME-2026-0001",
  "sourceAmount": "1000.00",
  "sourceCurrency": "USD",
  "destinationAmount": "82500.00",
  "destinationCurrency": "INR",
  "paymentType": "cross_border",
  "paymentSubtype": "usd_inr",
  "beneficiaryAccount": "IN1234567890",
  "beneficiaryName": "Ravi Kumar",
  "clientCompletedAt": "2026-05-12T10:00:00Z",
  "statusOnClientSide": "completed"
}
ReconLayer creates a PaymentIntent and, atomically, a ReconciliationCase with status: 'open', reconciliationStatus: 'unreconciled', and expectedAmount: "1000.00". The response returns:
{
  "caseId": "rc_001",
  "paymentIntentId": "pi_001",
  "externalReference": "ACME-2026-0001",
  "status": "reconciling",
  "verdict": null,
  "outcome": "created"
}
verdict: null because no evidence has arrived yet β€” there’s nothing to reconcile against.Calling this endpoint again with the same externalReference and the same canonical fields is idempotent: ReconLayer treats it as a replay (outcome: "reused") and reuses the existing case. If the canonical fields (amounts, currencies, beneficiary account) don’t match, the API returns a conflict describing exactly which fields differ.
2

Provider evidence arrives

A payout provider sends a webhook (or you call POST /v1/evidence/provider) confirming a transfer. ReconLayer:
  1. Stores the payload as a RawRecord (source: "webhook", sourceType: "psp_report", provider: "bridge").
  2. Normalizes it into a FlowLeg (type: "provider_transfer", phase: "intermediary_in", status: "confirmed", providerTransferId: "tr_bridge_001", amount: "1000.00", currency: "USD").
  3. The matcher looks for a PaymentIntent whose PaymentIntentReference has type: "provider_transfer_id" and value: "tr_bridge_001" β€” or whose externalReference/FlowLegReference{type: "external_reference"} matches a reference found in the leg or payload.
  4. On a match, it creates a MatchLink (matchType: "provider_id", confidence: "deterministic") and an AuditEvent (eventType: "match.created").
  5. The case is re-evaluated: reconcile() compares expectedAmount to the selected leg’s amount, computes unexplainedDelta, and updates reconciliationStatus.
3

On-chain evidence arrives

A second leg β€” the actual on-chain transfer β€” arrives via POST /v1/evidence/onchain or an on-chain integration feed. This becomes a FlowLeg with type: "onchain_transfer", phase: "transfer", txHash, chainId, fromAddress, toAddress, tokenAddress. If a PaymentIntentReference{ type: "tx_hash" } matches, a MatchLink with matchType: "tx_hash" is created.
4

The destination bank file arrives

A bank statement file (sourceType: "bank_statement", e.g. via an ImportProfile for icici:daily-xlsx) is imported. The matching row becomes a RawRecord and a FlowLeg (type: "bank_transfer", phase: "destination", currency: "INR", amount: "82500.00"), carrying a FlowLegReference{ type: "bank_reference", value: "BANK-OUT-789" }. If that reference matches the expected payment, a MatchLink with matchType: "reference_exact" is created.
5

The case reconciles

Once the required legs are present, reconcile() evaluates the selected leg (the one closest to phase: "destination" with status: "confirmed") against expectedAmount. If unexplainedDelta is 0 (or within the configured amountTolerance), reconciliationStatus becomes reconciled, reconciledAt is set, and the derived verdict becomes matched (or matched_with_exception if an exceptionType is also set).

Reading the result: verdicts and exceptions

ReconciliationCase does not store a verdict column β€” it’s derived from reconciliationStatus and exceptionType every time a case is read:
reconciliationStatusexceptionTypederived verdict
reconcilednullmatched
reconciledsetmatched_with_exception
tentatively_reconciled(any)needs_review
unreconciled(any)unreconciled
The case export endpoint additionally supports sla_risk and delayed as verdict filter values β€” slaRisk is a separate computed boolean (status: 'open' and open for more than 240 minutes). exceptionType is one of: missing_expected_record, missing_evidence, missing_callback, asset_mismatch, amount_mismatch, fee_variance, fx_variance, unexplained_variance, state_mismatch, timing_delay, settlement_reversed, invalid_signature, duplicate_record, chain_reorg.

What governs matching and tolerances

Two things make reconciliation organization-specific rather than hardcoded:
  • ReconciliationRule rows configure amount tolerance, matching time windows, per-phase SLA minutes, and which match strategies (provider_id, tx_hash, reference_exact, amount_and_time_window) are enabled. See Reconciliation Rules.
  • The canonical field registry defines exactly which fields each evidence source type can populate, so import profiles and adapters all normalize into the same shapes. See Canonical Field Registry.

Where to go next

Core Concepts

Full field-level reference for PaymentIntent, RawRecord, FlowLeg, FlowLegReference, and ReconciliationCase.

Reconciliation Rules

How tolerances, time windows, SLAs, and match strategies are configured per organization.

Database Architecture

The Prisma schema and table relationships behind this model.

Create or reuse a payment intent

The endpoint that starts the expectation-first flow.