v2.41.0
Release date: May 27, 2026
New features
Event delivery tracking and single-event retry (alpha)
This feature is in alpha. The API shape, field names, and filtering options may change in future releases — including breaking changes — before the feature reaches general availability. It is gated behind feature flags that are off by default in all environments.
Bridges and effects now expose two new endpoints that give visibility into individual event delivery attempts and allow retrying a single failed event without reactivating the entire bridge or effect.
List event deliveries
GET /v2/bridges/:handle/events
GET /v2/effects/:handle/eventsReturns a paginated list of delivery records. Each record represents one delivery — a stable identity shared across all retry attempts for the same event to the same destination. Key fields:
| Field | Description |
|---|---|
data.handle | Delivery UUID (stable across all retries) |
data.bridge | Bridge handle (bridge deliveries only) |
data.effect | Effect handle (effect deliveries only) |
data.record | Type of the record that triggered the delivery (e.g. intent, anchor) |
data.linked | Handle of that triggering record |
meta.status | pending, delivered, failed, or cancelled |
meta.replay | Number of completed delivery attempts so far |
Retry a single failed event
POST /v2/bridges/:handle/events/retry
POST /v2/effects/:handle/events/retryPass the event handle in the request body to redeliver one specific failed event:
{
"data": { "handle": "<event-uuid>" }
}For a bulk retry, omit data.handle and pass maxAge (minutes) instead — every failed delivery whose last attempt is within that window is re-queued:
{
"maxAge": 60
}When both are present, data.handle wins and maxAge is ignored: the specific delivery is retried and the bulk scope is skipped.
Filter event deliveries
The list endpoints accept the standard filter query parameter (see queries). Common filterable paths:
| Path | Example | Matches |
|---|---|---|
meta.status | "failed" | Delivery status |
data.record + data.linked | "intent" + "my-intent" | All deliveries tied to a specific record |
meta.output.data.signal | "intent-created" | Event signal (effect deliveries only) |
Example — every bridge delivery (DTC messages and effect events) tied to one intent:
GET /v2/bridges/my-bridge/events?filter={"data.record":"intent","data.linked":"my-intent"}Byte-identical wire fidelity
Each record carries meta.output — the exact signed LedgerRecord envelope that was sent to the webhook or bridge. The bytes are replayed identically on every retry, and the proofs in meta.output.meta.proofs cover that envelope, so integrators can reconcile a received wire body against meta.output byte-for-byte and verify the signature against their trusted ledger public key.
Proof chain
meta.proofs on the delivery record is appended in strict state-machine order, one trio (pending → running → delivered | failed) per attempt. The chain reads as a full lifecycle transcript.
A successful delivery (first attempt or after retries):
pending → running → failed → pending → running → deliveredA delivery the ledger gives up on (retry cap exhausted or target returned HTTP 501 Not Implemented) — the final failed records the target's error and a trailing cancelled proof records the ledger's own reason for stopping:
pending → running → failed → … → pending → running → failed → cancelledEvery proof's custom follows a uniform shape at the root: {status, moment, reason?, detail?}. Variant-specific fields (httpStatus, body, message, code) live under detail. reason is a LedgerErrorReason from the new delivery.* namespace: on failed proofs it's delivery.target-rejected (target replied non-2XX; detail.httpStatus + optional detail.body with the truncated payload), delivery.target-unreachable (timeout / DNS / connection-refused; detail.message + optional detail.code), or delivery.unexpected-error (unexpected ledger-side error; detail.message + optional detail.reason when a structured ledger error was thrown). On cancelled proofs reason is delivery.retry-cap-exhausted or delivery.permanent-failure — no detail.
See Inspect event deliveries for the full usage guide, filter catalogue, and proof-chain investigation workflow.
Planned changes before GA
This surface is intentionally small and will grow before it leaves alpha:
maxAgeon the retry body is a stopgap and will be replaced by ameta.momentrange filter, so retry scope is expressed with the same filter grammar as list instead of a separate parameter.data.handleon the retry body currently accepts a single string; future revisions will support the standard filter operators (e.g.$in) so callers can retry several specific deliveries in one call.- The filterable-field surface on list will expand to cover more columns as usage patterns emerge (e.g.
meta.replayfor retry-count queries).