Platform Event
A Platform Event tells an integration what happened through a named, versioned message, so it need not interpret raw chain logs.
The catalog decides what an event is called and what it contains. Everything downstream copies that decision.
An integration reads what happened without polling the chain or re-deriving finality itself: one list binds every event name and version to a payload shape, and registering the same pair twice fails at startup.
Every event names its lifecycle state. Read it with the event type: final can mean chain finality or a committed platform outcome. A final transaction-request event does not itself confirm a token movement.
Delivery retries can produce duplicates. Deduplicate on the event identifier and reconcile missing or retracted events.
Why an integration needs a catalog, not raw logs
A platform event is how an integration learns what happened on the platform: what occurred, when, and how settled that fact is. A chain log answers the first two on its own. It says nothing about the third, because the block behind it can still be reorganised.
The alternative to the platform's event vocabulary is an integration that reads raw logs itself. It decides, on its own, how deep a block must sit before a fact counts as final, and what to do when a block it already trusted gets replaced. Made once, by the platform, that decision is a fact every subscriber can check. Made independently by every integration, it is a second reading of the same chain, taken on that integration's own schedule and its own judgment of depth.
Asset Lifecycle names the general risk in that second reading. Spreading one instrument's issuance, custody, trading and reporting across separate systems turns every boundary between them into a reconciliation, and a reconciliation is where an instrument's ledger state and its legal record drift apart. A platform event holds that boundary at one place: what the integration was told, checked against what the platform's own indexed record says happened, from a name and a payload shape fixed by one registered list.
That list is not tied to one stage of an instrument's life the way a coupon payment belongs to servicing. Issuance, servicing and an eventual retirement each produce their own facts, and each one reaches an integration through this same catalog, tagged with one of five lifecycle states.
Why this catalog lives on the platform, not in a tenant's own indexer
The alternative to a platform-run catalog is a tenant indexer running beside the platform: watching the same chain, choosing its own reorg depth, and turning raw logs into its own vocabulary. That second indexer answers the same question the platform already answers, from a read of the chain that only the tenant sees.
This catalog holds no second reading, because the same indexer that decides a chain fact for the platform's own record is the one that derives the outbox row a subscriber later receives. The event and the indexed fact it describes come from that one decision, not from two.
For a financial institution operating a regulated instrument, two different readings of the same chain fact would leave the integration's own reconciliation and the platform's ledger starting from different guesses instead of the same registered description.
A vocabulary that cannot move under you
A platform event is one entry in a vocabulary defined by a single registry. Each entry binds an event name and version to one payload shape. Registering the same name and version twice fails at startup.
That guarantee targets the two quiet ways an integration built on notifications usually breaks. A field changes shape and the consumer keeps parsing the old one. Or a name starts covering a slightly different occurrence and nobody tells the consumers. Both fail silently, and a reconciliation turns them up weeks later.
The platform generates the machine-readable event manifest, event types for the TypeScript SDK, and sample payloads from that registry. The event catalogue is written by hand and checked against the registry. It explains when each event is sent and what a receiver does with it. Payload schemas and examples stay in the machine-readable definitions. Registration alone does not prove the platform sends an event; the catalogue separates live events from reserved names.
An integration selects the published payload contract by event name and version. Read both fields before interpreting the payload.
The envelope states how settled a fact is
Every platform event has a lifecycle_state: pending, provisional, final, retracted, or recalled. Recalled is reserved; no live event uses it today. Many names end in a state, such as token.transfer.final, but others end in a word such as raised or published. Read the envelope's state and event type together. The lifecycle reference describes the live sequences and reserved states.
A state change is a new event, and so is a correction. Nothing ever edits a delivery already sent.
For chain observations, provisional means included but still within the configured reorg depth, and final means the source block has passed that depth. Current transfer producers emit provisional at inclusion and final after the depth is reached. A reorg may retract a delivered provisional event; it does not retract a final event.
A fact does not change state in place. The same movement reported first as provisional and later as final reaches you as separate events with separate identifiers. Watch for the later event rather than for a field changing on the earlier one.
A retraction reports that an earlier fact no longer stands. It can follow a chain reorganisation or a business reversal, such as a trade bust. The platform leaves the earlier delivery intact. The consumer contract defines how to match the correction, including when its supersedes field is absent or names an event you never received.
Pending reports a transaction request that the platform broadcast and that is not yet confirmed. A request outcome uses final, including a failure or cancellation. This is the request's outcome, not a token movement's chain finality. Recalled is a reserved state. The platform sends no event with that state today. A recall request is refused because no live event has a recalled counterpart.
From one log to one row per subscriber
For an event derived from a chain log, the indexer writes one outbox row for every endpoint whose subscription matches the event name, in the same database transaction as the indexed fact. No queue sits in memory between the two, so the event and the indexed state it describes commit together or not at all.
Transaction-request events describe a different source: a state change recorded inside the platform. The platform records the event together with that state change, rather than deriving it from an indexed chain log.
The fan-out is per subscriber, not per fact. Ten subscribed endpoints mean ten rows from one log, each signed on its own and each with its own identifier, so one endpoint's delivery history says nothing about another's.
Each subject area the indexer writes decides for itself what a failure to write those rows should do, and the areas disagree. Read one path and assume the other, and the difference will surprise you.
Token movement
The outbox write runs in a nested savepoint. When it fails, the platform logs the failure and commits the indexed movement anyway, and you never receive that notification. A notification never holds the ledger hostage. The cost is that your record can lag the chain silently, with no signal on your end that it happened.
Identity and role changes
An outbox failure propagates and blocks the indexed write, so the whole unit retries together. When the retry succeeds, the indexed change and its notification land in that same commit, so a caller never sees one without the other. No gap can open between the indexed fact and its notification. The cost is that a notification problem delays the indexed record too.
Each endpoint has its own delivery sequence. A later eligible event waits while an earlier event remains pending, including during retry backoff. A linked correction waits until its cause was delivered or can no longer be delivered. Events waiting for chain finality do not block later events. A Replay can deliver an older observation after a newer one, so keep the newest state you hold. The ordering contract states the limits.
Writing a consumer that stays correct
Three properties decide whether a consumer stays correct. It subscribes only to the states it intends to handle, deduplicates on the event identifier, and applies a superseding event by looking up the identifier that event names rather than assuming order.
An endpoint subscribes with patterns. A pattern matches a name prefix, as in token.transfer.*, a name suffix, as in *.final, or one exact name. The defaults match names ending in .provisional, .final, and .retracted, not the envelope's lifecycle state. The subscriptions reference lists live events these defaults skip. A pattern for the reserved pending state delivers nothing today. A subscription change replays no earlier events; Replay is a separate request.
Store it and check it before you act. DeliveryWebhook Delivery retries a signed notification for three days and records whether a counterparty was told, so an integrator skips its own retry logic.Read the concept, the signed HTTPS POST that carries one event to one subscribed endpoint, uses bounded retries: a retry after a lost acknowledgement can repeat a delivery your endpoint already processed. Nothing on the platform side deduplicates for you.
Treat an event as an append-only record rather than as an update. When a later event supersedes an earlier one, it includes a supersedes field naming the identifier it replaces, and the platform never rewrites the original delivery. That field holds one identifier, never a list, so a superseding event always points at exactly one predecessor. A consumer that applies a retraction by looking up the superseded identifier and reversing its effect stays correct through a reorganisation. A consumer that overwrites in place cannot tell the difference between a correction and a fresh occurrence.
The platform publishes machine-readable definitions as a subscription manifest, the DALP events manifest, with a version marker that identifies the inventory it describes. Pin that marker if you generate code from the manifest, so an inventory change is a decision you make rather than one that arrives during a deploy.
Frequently asked questions
Does subscribing to an event name guarantee the platform sends it?
No. The internal corpus retains unavailable event schemas for historical decoding and future producers. The published manifest lists only event names with a composed producer. At present, live names include the access-control role events, asset creation, burn, issuance and pause events, compliance freeze and unfreeze events, identity registration events, the transfer events token.transfer.provisional, token.transfer.final version 2 and token.transfer.retracted version 2, the identity claim events, the provider verdict event, the trading venue order status event, token sale events, identity status, custody events, and the eleven XvP settlement events under xvp.*.final. Compliance recall, settlement retraction, and pre-inclusion pending names have no active producer.
Can a retracted or recalled event itself be retracted later?
No. A chain reorg retraction supersedes a provisional observation only. A retracted or recalled event is not itself retracted by a later chain reorganisation.
Do two endpoints see the same identifier for the same occurrence?
No. An identifier names one endpoint's delivery record, not the occurrence itself, which is why deduplication works per endpoint rather than across endpoints.
Reporting Obligation
A reporting obligation lets your organisation prove to a regulator which rules governed a filing, and keeps that proof fixed even after the duty changes.
Webhook Delivery
Webhook Delivery retries a signed notification for three days and records whether a counterparty was told, so an integrator skips its own retry logic.