LendEasy/Docs
Get API keys
GuidesLoansPayments & repayments

Payments & repayments

Follow borrower-authorized money from intent through rail settlement, value-dated ledger posting, allocation, receipt, and provider-driven returns.

A payment is both an operational intent and, later, a ledger transaction. Keeping those records separate prevents “accepted by the processor” from being mistaken for settled money. Payments are loan-scoped: every intent lives under /v1/loans/{loanId}/payments.

Payment lifecycle

SETTLED_PENDING_POST is intentionally visible: money has settled, but the loan balance is not authoritative until ledger posting succeeds. Money received outside LendEasy enters as RECORDED_EXTERNAL.

There is no public cancel route. CANCELLED is a system outcome—for example, cancelling autopay sweeps that enrollment’s pending unsubmitted intents.

Create a payment

Harbor’s first $517.14 installment is due September 12, 2026. The borrower authorizes an ACH debit one day earlier from a verified, borrower-owned instrument.

curl -X POST "$BASE/v1/loans/7204/payments" \
  -H "Authorization: Bearer $TOKEN" \
  -H "LendEasy-Tenant: demo-lender" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: harbor-2026-09-installment" \
  -d '{
    "instrumentId": 704,
    "amount": "517.14",
    "currency": "USD",
    "rail": "ACH",
    "effectiveDate": "2026-09-12",
    "source": "BORROWER"
  }'
{ "paymentId": 9011, "loanRef": "7204", "status": "CREATED", "attemptNumber": 1 }

The 201 response carries a Location header and does not invent a transaction reference. A postedTransactionRef appears only after the debit reaches the settlement gate and posts.

Two guards run at creation: the overpayment guard rejects an amount that, together with other in-flight intents, exceeds the loan’s total outstanding (subject to the product’s overpayment tolerance), and a PAYMENT_HOLD restriction blocks creation outright.

Value date and in-flight protection

Suppose the ACH debit settles on September 15. Posting is backdated to effectiveDate: 2026-09-12, the date the borrower paid; a future effective date posts on that business date instead. Until settlement, the loan summary exposes the covering inFlightPaymentAmount.

Date Payment state Ledger balance changed? Delinquency treatment
Sep 11 SUBMITTED No No suppression yet if effective date is future.
Sep 12 PROCESSING No $517.14 is recognized as covering amount in flight.
Sep 15 POSTED Yes, value-dated Sep 12 Schedule is satisfied as of Sep 12.

In-flight protection is provisional, scoped to the covering amount, and visible—not a hidden balance adjustment. If the debit fails or returns, the protection ends and downstream delinquency is recalculated from authoritative posted history.

Allocation on Harbor

Harbor’s first scheduled payment allocates $92.75 to interest and $424.39 to principal in this regular-period illustration.

First payment allocation

opening principal       $8,400.00
periodic interest          $92.75
payment                   $517.14
principal allocation      $424.39
closing principal       $7,975.61
Result$7,975.61 principal remaining

The payment service does not calculate this split independently. It asks the authoritative ledger to post and records the resulting transaction reference. That keeps API history, statements, payoff, and accounting on one result. GET /v1/loans/7204/payments/9011 returns the intent with its embedded state timeline:

{
  "paymentId": 9011,
  "loanRef": "7204",
  "status": "POSTED",
  "attemptNumber": 1,
  "amount": "517.14",
  "currency": "USD",
  "rail": "ACH",
  "effectiveDate": "2026-09-12",
  "source": "BORROWER",
  "createdAt": "2026-09-11T17:03:00Z",
  "postedTransactionRef": "30411",
  "timeline": [
    { "occurredAt": "2026-09-11T17:03:00Z", "source": "LMS", "state": "SUBMITTED" },
    { "occurredAt": "2026-09-15T14:22:08Z", "source": "LMS", "state": "POSTED", "postedTransactionRef": "30411" }
  ]
}

GET /v1/loans/7204/payments lists intents with status, source, and rail filters in an { "items": [...] } envelope. There is no separate timeline route—the timeline is part of the payment read.

External payments

Use POST /v1/loans/{loanId}/payments/record-external for a lockbox item, wire, branch payment, or payment collected by another authorized system. The request carries the amount, effectiveDate, a unique externalRecordRef, a source of EXTERNAL, IMPORT, or STAFF, and a required rationale; STAFF records also require an evidenceRef. LendEasy moves no money—the intent is created as RECORDED_EXTERNAL and flows into reconciliation, while posting, allocation, and receipts work the same as rail payments.

Returns

There is no public return route: returns arrive from the provider. A return reverses the posted allocation on the ledger with explicit linkage, and the payment moves to RETURNED carrying the return code, the returned amount, and any scheduled retry.

Return-code policy then decides what happens next—whether a retry is allowed, whether the instrument is invalidated, and how autopay reacts. A retry is always a new intent linked to the original through retryRootPaymentId with an incremented attemptNumber; the returned payment is never resubmitted. An unfamiliar return code fails closed into review and blocks secondary automation. The per-rail policy and its return-code table are described in autopay failure policies.

Receipts

GET /v1/loans/{loanId}/payments/{paymentId}/receipt streams a receipt PDF as an attachment, and only for a POSTED payment—requesting one for any other status fails validation. The receipt shows the posted amount, value date, rail, and transaction reference. A later return does not alter an issued receipt; the payment timeline shows what happened next.

Idempotency and concurrency

  • The Idempotency-Key header is optional (1–50 characters). Replaying a completed request returns the original result with x-served-from-cache: true.
  • An in-flight duplicate returns 409 with Retry-After.
  • Payment creation binds the key to a payload fingerprint; reusing a key with a different body fails with 422.
  • Provider events deduplicate by provider event ID and payment intent, and posting uses a ledger-side uniqueness guard, so concurrent callbacks cannot double-post.
  • The overpayment guard is evaluated at creation against current outstanding and in-flight amounts, not against a stale client-side figure.

Reconciliation invariant

Every terminal payment is checked across provider settlement, payment intent, and ledger transaction. A settled debit with no posting raises a SETTLED_NOT_POSTED exception; it is not resubmitted to the rail. A posting with no settlement evidence is a critical drift exception. See Reconciliation & drift.

Unified search across guides, recipes & the API referenceEsc