Payment Reconciliation API Developer Guide: Architecture, Matching, and Webhooks
Building reconciliation into a financial platform is not a reporting problem. It is an infrastructure problem. You are matching two streams of data — what your application recorded and what your payment processor, bank, or partner settled — and reconciling them in real time, at volume, without human intervention.
This guide walks through the architecture of a reconciliation API and programmable ledger API: data models, core endpoints, request/response patterns, matching workflows, and webhook design. It is written for engineers building financial infrastructure in fintechs, marketplaces, and embedded finance platforms.
What a Reconciliation API Actually Does
A reconciliation API does four things:
Ingest raw financial events from multiple sources — your application events, processor settlement files, bank statements.
Normalize those events into a canonical transaction model, handling format differences, currency, timestamps, and reference ID conventions.
Match records across sources using deterministic rules first (exact ID match, amount + date match), falling back to probabilistic scoring when IDs diverge.
Surface exceptions — unmatched records, amount discrepancies, timing mismatches — for programmatic resolution or human review.
The ledger API sits on top of this matching layer: it gives you a programmable double-entry view of every settlement, with entries that you define — not entries mapped to a vendor's fixed schema.
See What is an AI Reconciliation Engine for an overview of the matching layer.
Data Model Overview
Three core objects underpin the API:
TransactionRecord
The normalized representation of a financial event from any source.
{
"id": "txn_01HXQ...",
"source": "stripe",
"source_ref": "ch_3PK...",
"amount": 4995,
"currency": "USD",
"direction": "credit",
"timestamp": "2024-11-14T18:42:00Z",
"status": "settled",
"metadata": {
"merchant_id": "mer_abc",
"order_id": "ord_789"
}
}
Source is the data provider (stripe, adyen, bank_wire, internal). Source_ref is the original identifier in that provider's system. Both are preserved — normalization does not overwrite origin references.
LedgerEntry
The financial record in your programmable ledger. You define the schema: which accounts exist, what fields a line carries, what metadata is required.
{
"id": "le_01HXQ...",
"ledger_id": "ldg_marketplace",
"debit_account": "acc_escrow",
"credit_account": "acc_merchant_payable",
"amount": 4995,
"currency": "USD",
"transaction_ref": "txn_01HXQ...",
"posted_at": "2024-11-14T18:42:00Z",
"state": "posted",
"tags": ["marketplace_payout", "net_settlement"]
}
The debit_account and credit_account are identifiers in your account hierarchy. Your schema defines what those mean — no forced mapping to a vendor category structure.
MatchResult
The output of the reconciliation engine for a given transaction pair.
{
"id": "match_01HXQ...",
"transaction_a": "txn_01HXQ...",
"transaction_b": "txn_01HYQ...",
"match_type": "deterministic",
"confidence": 1.0,
"matched_on": ["source_ref", "amount", "currency"],
"status": "matched",
"discrepancies": []
}
Match_type is deterministic when a rule fires on exact keys. Probabilistic when the engine scores on composite signals. Confidence is 1.0 only on exact deterministic matches.
Core API Endpoints
The reconciliation API exposes four primary surfaces: ingest, reconcile, ledger, and query.
POST /v1/transactions/ingest
Submit raw transaction events from any source. Accepts single events or batches up to 1,000 records.
POST /v1/transactions/ingest
Content-Type: application/json
Authorization: Bearer {api_key}
{
"source": "stripe",
"records": [
{
"source_ref": "ch_3PK...",
"amount": 4995,
"currency": "USD",
"direction": "credit",
"timestamp": "2024-11-14T18:42:00Z",
"metadata": { "order_id": "ord_789" }
}
]
}
200 OK
{
"ingested": 1,
"transaction_ids": ["txn_01HXQ..."],
"queued_for_matching": true
}
Ingested records are normalized and queued for the matching engine. The engine runs continuously — matching happens within seconds for most high-confidence pairs.
POST /v1/reconciliation/run
Trigger a reconciliation job over a time window or specific transaction set. Useful for batch settlement files or end-of-day runs.
POST /v1/reconciliation/run
{
"sources": ["stripe", "bank_wire"],
"window": {
"start": "2024-11-14T00:00:00Z",
"end": "2024-11-14T23:59:59Z"
},
"ledger_id": "ldg_marketplace",
"options": {
"match_strategy": "deterministic_first",
"confidence_threshold": 0.85
}
}
202 Accepted
{
"job_id": "recon_job_01HXQ...",
"status": "running",
"estimated_records": 3842,
"webhook_url": "/webhooks/reconciliation.completed"
}
GET /v1/reconciliation/matches
Query match results with filtering. Returns matched pairs, unmatched records, and exceptions.
GET /v1/reconciliation/matches?job_id=recon_job_01HXQ...&status=unmatched
200 OK
{
"total": 47,
"records": [
{
"transaction_id": "txn_01HZQ...",
"source": "stripe",
"amount": 2499,
"status": "unmatched",
"exception_id": "exc_01HZQ...",
"reason": "no_counterpart_found"
}
]
}
POST /v1/ledger/entries
Create ledger entries programmatically. Can be triggered by match events or called directly for manual postings.
POST /v1/ledger/entries
{
"ledger_id": "ldg_marketplace",
"entries": [
{
"debit_account": "acc_escrow",
"credit_account": "acc_merchant_payable",
"amount": 4995,
"currency": "USD",
"transaction_ref": "txn_01HXQ...",
"posted_at": "2024-11-14T18:42:00Z"
}
]
}
Matching Workflow
The engine applies matching strategies in order, stopping at the first successful match.
Step 1 — Deterministic Matching
Deterministic rules fire on exact key matches. You configure which fields constitute a match for each source pair:
{
"rule_id": "stripe_bank_exact",
"source_a": "stripe",
"source_b": "bank_wire",
"match_keys": ["amount", "currency", "source_ref"],
"tolerance": { "timestamp_seconds": 86400 }
}
If a pair matches on all configured keys within the timestamp tolerance, the engine assigns confidence 1.0 and marks the pair matched. No probabilistic scoring runs.
Step 2 — Probabilistic Matching
When deterministic rules find no match, the engine applies a scoring model across available signals: amount proximity, timestamp delta, partial reference matches, metadata field overlap. Each signal contributes a weighted score. Pairs above the confidence threshold (default 0.85, configurable) are auto-matched.
Pairs below threshold are flagged as exceptions. The exception carries the highest-scoring candidate so your ops team has a starting point for manual resolution.
Step 3 — Exception Creation
Records with no match and no high-confidence candidate become exceptions:
{
"id": "exc_01HZQ...",
"transaction_id": "txn_01HZQ...",
"exception_type": "unmatched",
"candidates": [
{
"transaction_id": "txn_01HYQ...",
"confidence": 0.71,
"signals": ["amount_exact", "timestamp_delta_3h", "partial_ref_match"]
}
],
"requires_review": true,
"created_at": "2024-11-14T19:15:00Z"
}
Exceptions are queryable via GET /v1/exceptions and resolvable via POST /v1/exceptions/{id}/resolve. Resolution creates the match record and the ledger entry in a single atomic operation.
Webhook Patterns
The reconciliation API emits events you subscribe to rather than requiring polling. Three event types cover the normal workflow:
reconciliation.completed
Fires when a reconciliation job finishes. Payload includes match summary and exception count.
{
"event": "reconciliation.completed",
"job_id": "recon_job_01HXQ...",
"ledger_id": "ldg_marketplace",
"window": { "start": "2024-11-14T00:00:00Z", "end": "2024-11-14T23:59:59Z" },
"summary": {
"total_processed": 3842,
"matched": 3795,
"unmatched": 47,
"match_rate": 0.9878
}
}
match.failed
Fires when the engine exhausts all strategies on a record without finding a match. Use this to trigger your ops queue or alert channel.
{
"event": "match.failed",
"exception_id": "exc_01HZQ...",
"transaction_id": "txn_01HZQ...",
"source": "bank_wire",
"amount": 2499,
"best_candidate_confidence": 0.71
}
ledger.entry.created
Fires on each ledger entry creation. Use downstream for balance updates, reporting pipelines, or audit trail integrations.
{
"event": "ledger.entry.created",
"entry_id": "le_01HXQ...",
"ledger_id": "ldg_marketplace",
"debit_account": "acc_escrow",
"credit_account": "acc_merchant_payable",
"amount": 4995,
"currency": "USD"
}
Webhook deliveries are signed with HMAC-SHA256. Retry logic uses exponential backoff with a 72-hour maximum retry window. Failed deliveries are queryable via the webhook delivery log.
Designing Your Financial Data Model
The most important architectural decision in a reconciliation API implementation is who owns the financial data model. A programmable ledger API lets you define the canonical schema — accounts, entry fields, metadata requirements — rather than mapping your business logic to a vendor's fixed structure.
Practical implications for platform engineers:
Account hierarchy: Define accounts that match how your platform thinks about money — not how a generic ledger categorizes transactions. A marketplace has escrow, merchant payable, and platform fee accounts. A lender has disbursement, principal, and interest accounts. These are different schemas.
Metadata requirements: Enforce at schema level what metadata every entry must carry. If every marketplace payout entry must have a merchant_id, make that a required field on the ledger — not an optional tag you chase down at audit time.
Immutability: Posted ledger entries are immutable. Corrections use reversal entries — a debit/credit pair that cancels the original and creates the corrected entry with full audit trail.
See FBO Account Architecture and Virtual Ledgers for a deep-dive on account structure design for marketplace and fintech platforms.
Common Integration Patterns
Real-Time Transaction Monitoring
For platforms processing payments in real time: ingest on every transaction event, run continuous matching, subscribe to match.failed to surface exceptions immediately. The ledger writes happen as matches confirm — no batch delay.
Batch Settlement Reconciliation
For platforms that receive end-of-day settlement files from processors or banks: ingest the settlement file via batch ingest, trigger a reconciliation run for that window, consume reconciliation.completed to drive downstream reporting. Exceptions surface within minutes of the run completing.
Multi-Source Normalization
When you process payments through multiple providers — Stripe for card, Adyen for international, ACH via bank — each source has different reference ID formats, amount conventions, and timestamp schemas. The normalization layer handles this before matching runs. You configure source-specific parsing rules once; the matching engine sees a uniform TransactionRecord regardless of origin.
Getting Started
NAYA's reconciliation API and programmable ledger are built for fintech infrastructure teams that need deterministic matching at scale, without building and maintaining the matching engine themselves.
See the reconciliation platform overview to understand how the matching layer and ledger integrate with your existing infrastructure.
If you are evaluating whether to build this infrastructure or buy it, the Build vs Buy Fintech Ledger Decision Framework covers the full architectural trade-off in depth.
Get technical insights weekly
Join 4,000+ fintech engineers receiving our best operational patterns.