Ledger API
The package page: prerequisites, versioning, what is not changing, and the full release history.
The Ledger API lets you issue invoices, capture payments against them, and refund captured funds over HTTPS. This page explains how releases are identified and pinned, and lists every release with its changes.
Prerequisites
- A secret key for the environment you call.
sk_test_keys are accepted only by the sandbox host, andsk_live_keys only by the production host. - A release to pin. Send it in the
Ledger-Versionheader on every request.
Versioning
The API uses dated releases, not SemVer. A release is identified by the
date it shipped, and an integration opts into one with the Ledger-Version
header:
Ledger-Version: 2026-09-01
Omitting the header pins the request to the release that was current when the account made its first API call, so an unmaintained integration never breaks on our schedule.
The client SDKs use SemVer, because an SDK is a dependency in your build and
needs a version range your package manager can reason about. The API is a
remote service, so it needs a pin that survives a deploy you did not perform.
@ledger/node@4.2.0 can target API release 2026-09-01; the two numbers are
not related and do not move together.
Only breaking changes wait for you to opt in. Additive changes reach every pinned release on the day they ship: new optional request fields, new response fields, new enum members in responses, new operations, and new webhooks. Clients must ignore unrecognized response fields and tolerate unrecognized enum members. An integration that fails closed on an unknown enum member breaks on an additive change.
What is not changing
These commitments come up at upgrade planning often enough to be stated here.
- A declined payment continues to return
201, not402. The HTTP status describes the fate of the request, not the commercial outcome of the attempt. Decision 1 of the design decisions records the reasoning and the cost it imposes on integrators arriving from other payment APIs. - Money stays an integer in the currency minor unit. There is no plan to add a decimal-string representation alongside it. Two representations of one amount is a reconciliation defect waiting for a mismatched parser.
invoice_numberstays gapless, and voided numbers are never reused. Auditors rely on the sequence, and a gap is meaningful information that renumbering would destroy.
Release history
Releases of this API are listed newest first. For day-to-day integration, stay on this page. Archive topics remain available for bookmarks and support lookups.
2026-09-01 (current)
Overview
Ledger API 2026-09-01 makes refunds that are still processing count against a
payment's refund headroom, adds the fields and events needed to track those
refunds, and corrects the status returned for a missing If-Match header.
This release changes refund behavior. Review the compatibility information
before you pin to it.
New Features
- Pending refund total:
Payment.amount_refund_pendingcarries the total of refunds that are stillprocessing. The remaining refund headroom on a payment isamount - amount_refunded - amount_refund_pending. - Refund retrieval and events:
GET /v1/refunds/{refund_id}returns a single refund, and therefund.succeededandrefund.failedwebhooks report when a refund reaches a terminal state. An integration that waits for one refund to finish before it issues the next can now observe that directly. - Refund settlement time:
Refund.settled_atrecords when a refund reached a terminal state, separately fromcreated_at, so reconciliation can align each refund to the period in which it completed.
Enhancements
- Refund headroom reservation: A refund that is still
processingreserves its amount against the payment. A new refund is accepted only if it fits in the remaining headroom, and the check and the reservation are a single atomic step. A request that does not fit returns422 refund_amount_exceeded. Previously, onlysucceededrefunds counted, so two concurrent ₹600 refunds against a ₹1,000 payment were both accepted and the payer received ₹1,200. - Refunded amount:
Payment.amount_refundedcounts onlysucceededrefunds, which is the money actually returned to the payer. It previously also counted refunds that were stillprocessing, so the field showed a pending refund as spent while the refund check ignored it.
Fixes
- Missing
If-Matchheader: When aPATCH /v1/invoices/{invoice_id}request has noIf-Matchheader, the API returns428 precondition_required. It previously returned412 precondition_failed, the status for a stale value, so a client could not tell the two conditions apart. This fix applies to every release.
Compatibility
The refund headroom and amount_refunded changes apply only to requests
pinned to 2026-09-01 or later. Accounts created before this date keep their
existing pin until they send Ledger-Version: 2026-09-01.
amount_refund_pending is returned only from this release, because on earlier
releases amount_refunded still includes pending refunds and returning both
would count them twice. GET /v1/refunds/{refund_id}, the two refund
webhooks, and settled_at are available on every release.
Review every read of amount_refunded, and every code path that can issue
more than one refund against a payment, before you pin to this release. Refer
to Migrating to 2026-09-01
for the full procedure, including a sandbox test.
Known Issues
- Several earlier SDK versions apply the
Ledger-Versionvalue set when the client is created and ignore a per-call override. Confirm the header on the outgoing request when you test this release.
Archive topic: Ledger API 2026-09-01
2026-06-15
New Features
- Payment success event: The
payment.succeededwebhook reports when a payment reachessucceeded. A payment that did not complete its invoice previously produced no event when it succeeded, so the only way to learn the outcome was to retrieve the payment. - Void reason:
Invoice.void_reasonreturns thereasonsupplied tovoidInvoice. The reason was previously stored but never returned, so an audit could not read it back through the API.
Compatibility
The changes in this release reach every pinned release. No change to
Ledger-Version is needed.
Invoice.amount_due is deprecated. It is superseded by
Invoice.amount_remaining, which has carried the same value since the initial
release; two names for one value left integrators asking which to trust.
amount_due continues to be populated in every release up to and including
the first dated release after 2027-06-15, and is then removed. Replace each
read of amount_due with amount_remaining before that release.
Archive topic: Ledger API 2026-06-15
2026-03-01
Initial Release
- Introduces invoices: create, retrieve, update, delete, finalize, void, and list.
- Introduces payments: create and retrieve, with the
processing,succeeded, andfailedoutcomes carried inPayment.statusrather than in the HTTP status code. - Introduces full and partial refunds against a
succeededpayment. - Introduces the
invoice.finalized,invoice.paid,invoice.voided, andpayment.failedwebhooks. - Introduces cursor pagination on
GET /v1/invoices. - Introduces
Idempotency-Key, required on everyPOSTthat creates a resource. - Introduces
If-MatchandETagoptimistic concurrency onPATCH /v1/invoices/{invoice_id}.
Archive topic: Ledger API 2026-03-01