Skip to main content

Reconciliation

Fieldproof never holds money, so there is no settlement to reconcile with us. Reconciliation means one thing: every payment.collected event pairs with exactly one credit in your own bank or gateway records, and vice versa. The API gives you three independent lanes to prove it, from real-time to month-end.

1. Event ↔ statement pairing (continuous)​

Every payment.collected carries what you need to find the matching credit:

FieldMeaningPair it against
amountIncremental INR rupees booked by this eventThe credit amount
modeupi, card, netbanking, wallet, online, link, neft, nach, cheque or razorpay — the partner-recorded mode in Mode A (usually upi), the mode you sent in Mode B (default link)Payment channel on your statement
referenceMode A: the UTR where extracted, otherwise null. Mode B: your gateway's payment id — the reference you confirmedUTR / gateway payment id
totalCollectedAbsolute total collected for the case so farRunning total for the loan
outstandingAfterAbsolute remaining outstanding for the caseYour loan balance
collectedAtWhen the payment was bookedValue date, when reference is null
eventId (envelope)Unique per event — evt_<24hex>Your dedupe key

Rules that keep this lane clean:

  • Dedupe on eventId. Delivery is at-least-once; the same event can arrive twice by webhook and again from the poll feed. One booking per eventId.
  • Never skip a payment.collected because of ordering. Sequences have legitimate gaps (they count event types you are not subscribed to). Book the money — it is idempotent on eventId / reference — and gate only your case-state fields on sequence. See Updating your LMS.
  • Mode B: your own confirm log is the first thing to reconcile. The event mirrors the payments/confirm you made, so a captured payment with no event means a confirm you never sent — or one that returned a 409 telling you to refund. Reconcile gateway captures → confirm log → events, in that order. Note that a 409 DUPLICATE_CONFIRMATION returns only caseId, not the original booking; the booking of record is the event or the case snapshot, never the 409 body.
  • Mode A: the event may not equal the partner's claim. The operator-verified amount is what is booked. A proof that is rejected produces no event at all.

2. Case snapshot (on demand)​

GET /api/v1/partner/cases/{sourceLoanNumber} is the authoritative per-loan view — use it to settle any single-loan question instantly, or to re-sync a loan after a sequence gap.

200
{
"success": true,
"case": {
"sourceLoanNumber": "LN-2026-0001",
"caseId": "CC-2026-2388ED42",
"status": "in_progress",
"isActive": true,
"assignedAt": "2026-08-12T04:10:00.000Z",
"totalCollected": 3000,
"outstandingAmount": 9500,
"visits": [
{ "visitAt": "2026-08-18T09:20:00.000Z", "outcome": "partial", "amountCollected": 3000,
"paymentMode": "upi", "paymentReference": "UTR2026081812345", "ptpDate": null }
],
"createdAt": "2026-08-11T18:30:00.000Z",
"updatedAt": "2026-08-18T09:30:12.000Z"
}
}
  • status is one of pending, in_progress, collected, closed_unrecovered; isActive tells you whether the case is still in the field.
  • totalCollected and outstandingAmount are absolute, in whole INR rupees.
  • visits holds the last 20 visits. In Mode A, a visit's amountCollected / paymentReference before operations verify the proof is the partner's unverified claim — informative, not bookable. Book from events only.
  • If a loan has had more than one case (closed, then re-pushed), the snapshot prefers the active case, otherwise the most recent one.
  • 404 CASE_NOT_FOUND for a loan you never pushed — and equally for another organisation's loan; the endpoint is not an existence oracle.

3. The poll feed as an independent audit lane​

GET /api/v1/partner/cases/updates returns the same event envelopes your webhook receives — every event type, whether or not you subscribed to it by webhook, oldest first, with all fields identical. That makes it the natural lane for a second, independent pass over any window:

GET /api/v1/partner/cases/updates?since=2026-08-01T00:00:00Z&limit=500
→ { "success": true, "events": [ … ], "nextCursor": "…", "hasMore": true }
GET /api/v1/partner/cases/updates?cursor=<nextCursor>&limit=500
…until hasMore is false
  • since (ISO datetime) is honoured on the first call only and filters by the time the event was received by the server; after that, follow nextCursor (opaque). If both are sent, cursor wins. limit defaults to 100, maximum 500.
  • Persist nextCursor only after you have processed the page.
  • Because the feed contains everything, it also lets you check for a payment.collected you fear a webhook lost — no replay needed. Details in Poll API.

4. Book-level (daily or weekly): fullSnapshot: true​

Push your complete open book with fullSnapshot: true (push API). Any case Fieldproof still holds active that is missing from your file is flagged for Fieldproof operations review (snapshot_dropped); you receive only the count — summary.flaggedNeedsReview — not the list. This catches loans that were settled directly with you and never recalled.

Send it as one single batch: chunking a full snapshot would flag every case absent from the chunk. Do it daily or weekly, never incrementally.

Month-end​

Your monthly bank or gateway statement is the audit pair for the month; the Fieldproof side of the pairing comes straight from the API — there is no separate statement file to wait for:

  1. Pull the month's events from the poll feed with since set to the first day of the month, and page to the end.
  2. Sum amount per sourceLoanNumber over the payment.collected events (deduped on eventId); compare against the credits on your statement.
  3. Spot-check the last totalCollected per loan against GET /cases/{sourceLoanNumber} for anything that does not agree.
  4. Anything you cannot pair — an event without a credit, or a credit without an event — goes to [email protected] with the eventId (or the UTR / gateway payment id) attached. That is a support escalation, not a normal month.