$ENGINE for the engine’s URL and $TOKEN for a bearer.
The event feed
GET /v1/events answers events in the order they were recorded, of two kinds:
journal, the ledger’s facts as NodeAsset reads them: a subscription created, an admission offered or granted, a NAV published, a settlement proposed. Each payload names the record your systems key on beside the contract: the subscription a lot is (subscriptionId, with itsappliedNavandclaimedAt), the registered holder an admission is for (holderId).control, what NodeAsset announces: its own acts (an operation that finished, an approval requested or decided, a reconciliation finding or hold, a restriction, a distribution that became payable, a reserve’s mint, redemption, report or change of terms), and each step of an order or a dealing cycle, below.
venue.events.tail-interval, which is off by default; until then the feed holds control events alone. The order and dealing-cycle steps are read off the ledger by the same reader, so they need it too. See Configuration reference.
Narrow it by kind, type and instrumentId, to one holder’s events with holderId, or to one record’s with resourceKind and resourceId, and window it by from and to. Page oldest first after a cursor: send each page’s next back as after, and keep the last one you processed, so a restart resumes where it stopped. An empty page keeps the cursor it was asked with.
curl
order=desc pages newest first, with the same after. A bearer with the monitor role reads control events only, each redacted to its ids, codes and counts.
Lifecycle events
Each step is announced once per record, at the ledger time andoffset of the transaction that made it, with the record’s ids, its units, its price and what it pays, and the transaction’s updateId.
amount and paymentInstrumentId are null for an order paid off the ledger. A step announced again, as when the reader goes over a range twice, is not stored twice: a redemption priced again keeps its first redemption.priced, and GET /v1/redemptions/{redemptionId} has the latest price.
Webhooks
The engine posts its announcements, the control events below, to each webhook that takes their type; operations and approvals stay in the feed. The venue configures a webhook on the engine (venue.recon.webhooks): an id, the url, a secret of at least 32 bytes, and the event types it takes (events; none listed takes every type). POST /v1/webhooks creates one under four eyes, naming a key file in the engine’s webhook secret directory (secretFile) so that no key crosses the API, and GET /v1/webhooks lists them all. POST /v1/webhooks/{webhookId}:test sends one test delivery and answers whether the receiver took it.
The engine posts only to a public address, or to an address in a network the venue allows for an internal receiver (Configuration). It checks the address at every delivery, so a name that later resolves to a private address is refused there.
Each delivery is a JSON POST:
Answer 2xx once you have taken it. Anything else, or no answer, is retried with exponential backoff until the webhook’s attempts run out. Deliveries are at least once, so dedupe on
X-NodeAsset-Delivery. Keep the last seq you took: after an outage, GET /v1/events?after=<seq> reads on from it, so nothing a webhook missed is lost.
Each type’s body is in the OpenAPI document, under
x-webhooks. A type your code does not know comes from a newer engine: treat it as a case of its own, never as an error.
Verify a delivery
Check every delivery before you act on it:- Verify the signature: the HMAC-SHA256, under the webhook’s secret, of the timestamp header, a
., and the body’s bytes as received, never re-serialized. Compare in constant time. - Refuse a timestamp more than five minutes from your own clock, so a captured delivery cannot be replayed later.
- Drop a delivery whose id you have already handled.
Java

