ReconciliationCase. Everything that flows into the system — API calls, provider webhooks, on-chain events, CSV uploads — ultimately resolves into one of two record types:
- A
PaymentIntent— an expectation that a payment will move. - A
FlowLeg— a normalized piece of evidence describing one leg of a payment’s actual journey (a bank credit, a provider transfer, an on-chain transfer, etc.), backed by an immutableRawRecord.
This page is the map. For the mechanics of each entry point, see File Imports, Inbound Webhooks, Outbound Webhooks, Bridge Integration, and On-chain Integrations.
The three ways evidence enters ReconLayer
Every evidence-shaped payload — whether it arrives as a Bridge webhook, aPOST /v1/evidence/provider call, an on-chain ingest, or a row in an imported file — is normalized into the same shape (a RawRecord plus zero or more NormalizedLegs) and passed through a single ingestEvidence pipeline. That pipeline:
- Checks for an existing
RawRecordwith the same(organizationId, source, sourceRef)and short-circuits as a duplicate if found. The response setsduplicate: trueand returns the originalrawRecordId,flowLegIds, andmatchedCaseIds. - Persists the payload as an immutable
RawRecordand logs anevidence.receivedaudit event. - Creates one or more
FlowLegrows (and anyFlowLegReferences) from the normalized legs, logging aleg.normalizedaudit event for each. - Runs the matching engine against open
ReconciliationCases — unless the record failed signature validation (signatureValid === false), in which case matching is skipped entirely and the evidence sits unmatched for manual review.
EvidenceAcceptanceResponse shape:
Flow 1: Expectation-first
Use this path when the thing arriving first tells ReconLayer what should happen. Typical sources:- A direct
POST /v1/payment-intentscall from your backend. - A
client_internal_ledgerrow in a file import.
Expectation is registered
A
PaymentIntent is created — directly via the API, or via an imported ledger row. ReconLayer opens a ReconciliationCase with status: open, logs a payment_intent.created audit event, and queues a payment_intent.created outbound webhook event for any subscribed endpoints.Evidence arrives later
A provider webhook, on-chain event, or file import produces a
RawRecord and one or more FlowLegs via ingestEvidence.Flow 2: Evidence-first
Use this path when the thing arriving first tells ReconLayer what actually happened, before any expectation was registered. Typical sources:- A Bridge webhook at
POST /webhooks/bridge/{integrationKey}. - A
bank_statement,psp_report, oronchain_reportrow in a file import. - An on-chain integration ingest (
POST /v1/onchain-integrations/{onchainIntegrationId}/ingest). - A direct
POST /v1/evidence/providerorPOST /v1/evidence/onchaincall.
Evidence arrives
The provider or chain payload is normalized and passed to
ingestEvidence, creating a RawRecord and one or more FlowLegs.Matching engine searches for an expectation
The engine looks for an open
ReconciliationCase / PaymentIntent that this evidence could satisfy.Matched
If a match is found, a
MatchLink is created linking the FlowLeg(s) to the ReconciliationCase, and the case is updated.Signature validation short-circuits matching
For evidence that carries a cryptographic signature (currently Bridge webhooks),ingestEvidence still creates the RawRecord and FlowLegs even if the signature is invalid — but it skips the matching step (matchedCaseIds will be empty) and marks the RawRecord with validationStatus: 'failed'. The HTTP response for POST /webhooks/bridge/{integrationKey} returns 400 invalid_webhook_signature in this case, while still reporting the rawRecordId and sourceRef that were persisted for audit purposes. See Inbound Webhooks for details.
Idempotency and duplicates
- The canonical evidence endpoints (
POST /v1/evidence/provider,POST /v1/evidence/onchain) require anIdempotency-Keyheader. Replaying the same key with the same body returns the original response with headerIdempotency-Replayed: true; replaying with a different body returns409 idempotency_key_conflict; a concurrent in-flight request with the same key returns409 idempotency_request_in_progress. - At the
ingestEvidencelayer — used by webhooks, polling, and imports too — duplicates are detected independently via the(organizationId, source, sourceRef)uniqueness constraint onRawRecord, so re-delivered webhooks, re-polled transfers, and re-uploaded files are all safe to retry. POST /v1/import-batchesis itself idempotent on file content: re-uploading the same bytes for the same organization returns the existingImportBatchwithduplicate: trueinstead of reprocessing rows.
Where each entry point lands
| Entry point | Source type | Produces |
|---|---|---|
POST /v1/payment-intents | — | PaymentIntent + ReconciliationCase |
POST /v1/import-batches (client_internal_ledger) | client_internal_ledger | PaymentIntent + ReconciliationCase |
POST /v1/import-batches (bank_statement / psp_report / onchain_report) | per profile | RawRecord + FlowLeg(s) |
POST /webhooks/bridge/{integrationKey} | psp_report (+ onchain_transfer leg when applicable) | RawRecord + FlowLeg(s) |
POST /v1/evidence/provider | psp_report | RawRecord + FlowLeg |
POST /v1/evidence/onchain | onchain_report | RawRecord + FlowLeg |
POST /v1/bridge-integrations/{id}/poll-transfer (+ background poll loop) | psp_report (API source) | RawRecord + FlowLeg(s) |
POST /v1/onchain-integrations/{id}/ingest | onchain_report | RawRecord + FlowLeg |
