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:
| Field | Meaning | Pair it against |
|---|---|---|
amount | Incremental INR rupees booked by this event | The credit amount |
mode | upi, 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 |
reference | Mode A: the UTR where extracted, otherwise null. Mode B: your gateway's payment id — the reference you confirmed | UTR / gateway payment id |
totalCollected | Absolute total collected for the case so far | Running total for the loan |
outstandingAfter | Absolute remaining outstanding for the case | Your loan balance |
collectedAt | When the payment was booked | Value 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 pereventId. - Never skip a
payment.collectedbecause of ordering. Sequences have legitimate gaps (they count event types you are not subscribed to). Book the money — it is idempotent oneventId/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/confirmyou made, so a captured payment with no event means a confirm you never sent — or one that returned a409telling you to refund. Reconcile gateway captures → confirm log → events, in that order. Note that a409 DUPLICATE_CONFIRMATIONreturns onlycaseId, 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.
{
"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"
}
}
statusis one ofpending,in_progress,collected,closed_unrecovered;isActivetells you whether the case is still in the field.totalCollectedandoutstandingAmountare absolute, in whole INR rupees.visitsholds the last 20 visits. In Mode A, a visit'samountCollected/paymentReferencebefore 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_FOUNDfor 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, follownextCursor(opaque). If both are sent,cursorwins.limitdefaults to 100, maximum 500.- Persist
nextCursoronly after you have processed the page. - Because the feed contains everything, it also lets you check for a
payment.collectedyou 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:
- Pull the month's events from the poll feed with
sinceset to the first day of the month, and page to the end. - Sum
amountpersourceLoanNumberover thepayment.collectedevents (deduped oneventId); compare against the credits on your statement. - Spot-check the last
totalCollectedper loan againstGET /cases/{sourceLoanNumber}for anything that does not agree. - 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.