Event reference
Every event — delivered by webhook or returned by the poll API — uses the same envelope. The webhook body and the poll-API item are byte-for-byte the same shape.
The envelope
{
"eventId": "evt_66a1f0c2d3e4f5a6b7c8d9e0",
"type": "payment.collected",
"occurredAt": "2026-08-17T09:31:00.000Z",
"version": 1,
"lenderCode": "YOURORG",
"sourceLoanNumber": "LN-2026-0001",
"sequence": 4,
"payload": { }
}
| Field | Type | Meaning |
|---|---|---|
eventId | string | evt_ + 24 hex chars. Globally unique — your dedupe key. |
type | string | One of the types below. Also sent as the X-Fieldproof-Event header. |
occurredAt | ISO 8601 datetime | When the change happened on our side. |
version | number | Envelope version. Currently 1. |
lenderCode | string | Your organisation code. |
sourceLoanNumber | string | null | Your loan number as pushed. null only on ping. |
sequence | integer | null | Per-case counter, starts at 1 for every new case, counts all event types (including ones you are not subscribed to). null only on ping. |
payload | object | Type-specific, below. |
All money values are whole INR rupees (numbers, no paise).
Subscription
The default subscription is case.received, payment.collected,
case.closed. case.assigned and visit.completed are opt-in — ask our
integration team to enable them (CRM Integrations tab); there is no self-serve
subscription API yet. Two consequences:
sequencestill counts the events you don't receive, so a default subscriber sees gaps (1 → 4 → 5). That is normal — see Ordering.- The poll API returns all types regardless of your webhook subscription.
case.received
Your pushed row is a live case in the network. Also the first event of a
fresh case when a loan whose previous case had closed is re-pushed — the
caseId changes and sequence restarts at 1.
{ "sourceLoanNumber": "LN-2026-0001", "caseId": "CC-2026-2388ED42", "status": "pending" }
| Field | Value |
|---|---|
status | always "pending" |
case.assigned (opt-in)
A field partner accepted the case. External status becomes in_progress.
{ "sourceLoanNumber": "LN-2026-0001", "caseId": "CC-2026-2388ED42", "assignedAt": "2026-08-17T06:10:11.000Z" }
visit.completed (opt-in)
A visit was recorded, whatever the outcome. The case stays in_progress.
{
"sourceLoanNumber": "LN-2026-0001",
"caseId": "CC-2026-2388ED42",
"outcome": "ptp",
"visitAt": "2026-08-17T08:45:02.000Z",
"ptpDate": "2026-08-20",
"notes": "Will pay after salary credit"
}
| Field | Type | Notes |
|---|---|---|
outcome | enum | See table below. |
visitAt | ISO datetime | When the visit was recorded. |
ptpDate | string | null | Committed pay-by date; set for ptp, null otherwise. |
notes | string | null | Partner's free-text note, may be null. |
outcome values:
outcome | Meaning |
|---|---|
delivered_acknowledged | Payment notice delivered and acknowledged by the borrower |
collected | Money collected in full during the visit |
partial | Part of the outstanding collected |
ptp | Promise to pay — ptpDate carries the committed date |
rtp | Refuse to pay — the borrower declined to pay |
refused | Borrower refused (also a case.closed reason when the case ends this way) |
not_available | Borrower not found at the address this visit |
wrong_address | Address does not lead to the borrower — push a corrected row |
shifted | Borrower has moved — push a corrected row |
A collected or partial outcome is the partner's claim; money is only
announced by payment.collected once it is verified/booked (below).
payment.collected
Money verifiably received on your rails. Fires per payment, with both the incremental amount and the absolute case totals.
{
"sourceLoanNumber": "LN-2026-0001",
"caseId": "CC-2026-2388ED42",
"amount": 3000,
"mode": "upi",
"reference": "UTR2026081712345",
"totalCollected": 3000,
"outstandingAfter": 9500,
"collectedAt": "2026-08-17T09:30:12.000Z"
}
| Field | Type | Notes |
|---|---|---|
amount | number (INR) | Incremental — this payment only. |
mode | enum | upi | card | netbanking | wallet | online | link | neft | nach | cheque | razorpay. Never cash (not accepted for external-org cases). |
reference | string | null | UTR / gateway payment id. null when no reference could be captured. |
totalCollected | number (INR) | Absolute total collected on this case after this payment. |
outstandingAfter | number (INR) | Absolute outstanding on this case after this payment. |
collectedAt | ISO datetime | When the payment was booked. |
When it fires and what mode / reference carry:
- Mode A — static QR: after our operations team
verifies the partner's payment proof.
amountis the operator-confirmed figure — it may differ from what the partner claimed; trust the event.modeis the partner-recorded mode (usuallyupi);referenceis the UTR where extracted. Rejected proofs emit nothing. - Mode B — your gateway: the event mirrors your
own
POST /cases/{loan}/payments/confirm.modeechoes the mode you sent (defaultlink);referenceis the gateway payment id you sent.
Money is never announced before it is booked. Use the absolute values to set
your loan record; use amount for your ledger row.
case.closed
Terminal — the last event of a case.
{
"sourceLoanNumber": "LN-2026-0001",
"caseId": "CC-2026-2388ED42",
"status": "collected",
"reason": "full_settlement",
"totalCollected": 12500
}
| Field | Type | Notes |
|---|---|---|
status | enum | collected | closed_unrecovered |
reason | enum | full_settlement | recalled | not_available | refused | written_off | legal_handover |
totalCollected | number (INR) | Absolute total collected on this case at closure. |
reason | status | Trigger |
|---|---|---|
full_settlement | collected | Payments reached full clearance (≥ 99 % of the remaining outstanding — 1 % tolerance). Preceded by a payment.collected. |
recalled | closed_unrecovered | You called POST /cases/{loan}/recall. |
not_available | closed_unrecovered | Closed after the borrower could not be found. |
refused | closed_unrecovered | Closed after the borrower refused to pay. |
written_off | closed_unrecovered | Written off — an admin action on the Fieldproof side. |
legal_handover | closed_unrecovered | Handed over for legal action — an admin action on the Fieldproof side. |
A closed loan can be re-pushed: that opens a new case with a new caseId
and sequence restarting at 1 (you will see case.received again).
verification.completed
:::note Address-verification product only
verification.completed belongs to the address-verification product. It is
not emitted for collection cases — if you only run collections you will
never receive it, and you can ignore the type. Its payload is documented with
that product.
:::
ping
Sent only by the webhook self-test (POST /api/v1/partner/webhooks/test).
The envelope is the same shape, but sourceLoanNumber and sequence are
null and the eventId has a ping_ marker:
{
"eventId": "evt_ping_9c4e21a7b3d0",
"type": "ping",
"occurredAt": "2026-08-17T09:00:00.000Z",
"version": 1,
"lenderCode": "YOURORG",
"sourceLoanNumber": null,
"sequence": null,
"payload": { "message": "<free text>" }
}
Verify the signature, return 2xx, apply nothing. Your JSON model must accept
the nulls.
Ready to consume these? Updating your LMS from our events has the complete, transactional apply handler with SQL.