Events & Webhooks
When something notable happens in your ledger, Ledgr records an event. Rather
than polling the API to find out what changed, you register a webhook
destination, a URL of your own, and Ledgr sends each matching event to it as it
happens. Delivery is signed so you can trust it, and retried so a brief outage on
your side does not lose an event. Events and destinations belong to your whole
account, so these endpoints do not take a program_id header.
What Ledgr sends events for
| Event | Sent when |
|---|---|
transaction.created |
A transaction is created, whether pending or posted. |
transaction.posted |
A transaction settles. |
transaction.reversed |
A transaction is reversed. |
transaction.failed, transaction.canceled |
A transaction ends without settling. |
transaction.disputed |
A transaction is marked as disputed. |
hold.created |
A hold is placed. |
hold.captured |
A hold is captured, in full or in part. |
hold.released |
A hold is released. |
The event payload
Each delivery is a JSON body in this shape:
{
"id": "evt_c421c1ed-0cbe-4b3b-827c-cf4a74987128",
"type": "transaction.posted",
"created": 1786045169,
"data": {
"transaction_group_id": "trx-grp_49202f18-7778-4949-8b06-c9d7b4d71e54",
"amount": 102,
"currency_code": "USD"
}
}
Confirming an event is genuine
Anyone can send a POST to your URL, so verify that a delivery really came from Ledgr before you act on it. Every delivery includes a signature header, along with headers naming the event type and delivery id:
X-Ledgr-Signature: t=1786045169,v1=4f0b3f2a...c9d7
The signature is an HMAC-SHA256 of the string "<t>.<raw_body>", keyed with the
destination's signing secret. Recompute it over the exact raw request body,
compare it to the header with a constant-time check, and reject deliveries whose
timestamp is too old so an intercepted request cannot be replayed later. You can
view or roll the signing secret on the destination's page.
import crypto from "crypto";
function verify(rawBody: string, header: string, secret: string): boolean {
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
Delivery and retries
Return a 2xx status to acknowledge a delivery. Any other status, or a timeout,
counts as a failure, and Ledgr retries with increasing gaps between attempts over
about an hour. Because a retry can arrive after your handler already ran, treat
the event id as something you have seen before: record which ids you have
processed and ignore repeats. That keeps your handling correct even under
retries.
Operations
| Method and path | What it does |
|---|---|
POST /webhooks |
Create a destination. |
GET /webhooks |
List destinations. |
GET /webhook |
Retrieve one destination, with recent delivery stats. |
PUT /webhooks |
Update a destination, such as enabling it or changing which events it receives. |
DELETE /webhooks |
Delete a destination. |
POST /webhooks/roll |
Issue a new signing secret for a destination. |
POST /webhooks/resend |
Send a delivery again. |
GET /webhooks/deliveries |
List the deliveries made to a destination. |
GET /events |
List events, each with a summary of how it was delivered. |
GET /event |
Retrieve one event with its payload and every delivery attempt. |
The API reference documents the parameters and responses for each of these.