> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stellarium.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Events: contracts that react across the network

> Declare event types on your nodes, emit them from your scripts, and run event contracts on any node with each event, paid for by whoever deployed them.

Stellarium **events** let [contracts](https://docs.stellarium.sh/concepts/contracts.md) react to each other across the network. Your organization declares an **event type** on one of its nodes, your scripts `emit` events of that type, and **event contracts** on any node run with each event they consume. Events need RequestScript 1.6.0. See [Events](https://docs.requestscript.org/contracts/events) in the RequestScript docs for the language rules.

* **Only the owner emits.** The organization that owns an event type is the only one that may emit it, from scripts running on its own nodes.
* **Anyone consumes.** Any organization may deploy an event contract for an event type, on any node, unless the type is `restricted`. A restricted event type is consumed only by contracts its owner deployed.
* **Consumers pay for themselves.** Whoever deployed an event contract pays for its runs, never the publisher.

## Declaring an event type

An event type declares the fields its events carry, and has no body:

```kotlin OrderPlaced.req theme={null}
event com.example.OrderPlaced(orderId: string, total: decimal)
```

Send it to [`POST /v1/events`](https://docs.stellarium.sh/node/http-api#events) on a node of your organization, with the node's `API_KEY` or a run token from one of your organization's [API clients](https://docs.stellarium.sh/concepts/api-clients.md) for that node:

```bash theme={null}
curl -X POST https://node.example.com/v1/events \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"source": "event com.example.OrderPlaced(orderId: string, total: decimal)"}'
```

```json Response theme={null}
{
  "path": "com.example",
  "name": "OrderPlaced",
  "version": "1",
  "fields": [{ "name": "orderId", "type": "string" }, { "name": "total", "type": "decimal" }],
  "restricted": false,
  "ownerOrganizationId": "org_..."
}
```

* **The node's organization owns it.** An event type belongs to the organization of the node it is declared on, because only that organization's nodes may emit it. A run token from another organization is refused with `403`.
* **Versions are immutable.** Deploy a new version, such as `event com.example.OrderPlaced@2(...)`, to change it. Deploying a version that exists is refused with `409`.
* **Names are taken once.** The first node to declare a path keeps it. Peers refuse the same name from another node.
* **Peers learn about it right away.** The node announces its event types to its peers after every deploy and delete.

## Emitting events

A request or a contract emits an event with `emit`, giving every field:

```kotlin Checkout.req theme={null}
contract com.example.Checkout(orderId: string, total: decimal): string {
    emit com.example.OrderPlaced(orderId: orderId, total: total)
    return orderId
}
```

* **Events are published only if the run succeeds**, as a contract's state is saved only if its call succeeds. A run that fails publishes nothing.
* **Only your organization's scripts emit.** A script may emit your event type when it runs on one of your organization's nodes and belongs to your organization: a run with the node's `API_KEY` or one of your API clients, or a contract your organization owns. Other organizations can still trigger your events by calling your contracts, such as `Checkout`, which emit as your organization.
* **A run may emit at most 16 events**, each with at most 32 KiB of data as JSON. A run that exceeds either fails. Set `maxEventsPerRun` and `maxEventDataBytes` in the node's [configuration](https://docs.stellarium.sh/node/configuration.md) to change them.

A script that may not emit an event type fails when it reaches the `emit`, and `POST /v1/run` answers `422` with `"error": "Script failed"`.

## Consuming events

An event contract names the event type it `consumes`. It declares no parameters, since the event's fields are its parameters, and no return type:

```kotlin OnOrderPlaced.req theme={null}
contract com.example.OnOrderPlaced consumes com.example.OrderPlaced {
    var state count: int32 = 0
    var state lastOrder: string = ""

    count = count + 1
    lastOrder = "${orderId} from ${caller.org}"
}
```

Deploy it with [`POST /v1/contracts`](https://docs.stellarium.sh/node/http-api#contracts), like any contract, on any node that knows the event type.

* **It consumes one version.** An event contract consumes the version it names, or the latest version its node knows when you deploy it.
* **It isn't a resource.** It runs only with events, so it doesn't appear in `GET /v1/resources`, and nobody can call it with made-up data. A contract keeps its kind across versions: an event contract can't get a callable version, or the other way around.
* **It reads the event.** Besides the fields, it reads the [built-in variables](https://docs.stellarium.sh/concepts/built-in-variables.md#event) `event` (`id`, `type`, `version`, `publishedAt` and `depth`) and `caller`, the event's owner and the node that published it.
* **It keeps state** like any contract, saved only if its run succeeds.
* **A restricted event type** can only be consumed by its owner's contracts. Any other organization's deploy is refused with `403`.

## How events reach your contract

```mermaid theme={null}
sequenceDiagram
  participant P as Publishing node
  participant C as Consuming node
  participant O as Operator
  C->>P: Announce event contracts (signed)
  P->>P: Run emits an event, run succeeds
  P->>C: POST /v1/events/deliver (signed, membership token)
  C->>C: Check signature and membership, queue the event
  C-->>P: 202 Accepted
  C->>O: Hold a run under the event mandate
  C->>C: Run the event contract
  C->>O: Capture the tokens executed
```

1. **Nodes announce what they have.** Each node tells its peers which event types it declared and which event contracts it runs, signed with its key. A node joining the network asks each peer for its announcement.
2. **The publisher queues each event.** When a run succeeds, its node signs each event with its key and attaches its **membership token**. It queues the event for its own event contracts, and once for each peer that runs event contracts consuming it.
3. **The publisher delivers it.** Each peer gets the event at `POST /v1/events/deliver`. The publisher retries with backoff, from 30 seconds up to an hour between attempts, until the peer accepts the event or the event is a day old.
4. **The receiver checks it.** The receiving node accepts the event only if its signature is the publisher's, the operator's membership token says the publisher belongs to the event type's owner, it was published in the last day, and the node knows the event type.
5. **The receiver runs its event contracts.** Each one runs once per event, even if the event is delivered twice. A run that fails is tried again, up to 3 times in all.

Events may arrive in any order. Restricted events are delivered only to event contracts their owner deployed. A node's admin can follow both queues with the [admin routes](https://docs.stellarium.sh/node/http-api.md#admin-routes).

### Membership tokens

When a node starts a session with the operator, and every five minutes after, it gets a **membership token**: the operator's signed statement that the node belongs to its organization. It is a JWT signed with the same keys as run tokens, with `aud` `stellarium:membership`, `sub` the node's public key, `org` its organization and `use` `membership`, and it lasts an hour. It can never be used as a run token, and a run token can never be used as one.

A node needs a membership token to emit events, and an `OPERATOR_URL` to check the tokens of events it receives.

## Who pays

Emitting costs only the publishing run's tokens. Each event contract's run is paid by the organization that deployed it:

* **Free on your own nodes.** An event contract deployed with the node's `API_KEY`, or by the node's own organization, runs free.
* **Billed under a mandate elsewhere.** When your API client deploys an event contract to another organization's node, the node asks the operator for an **event mandate** with the client's run token: your permission for the node to run that contract and bill you. Before each event, the node asks the operator to hold a run under the mandate. It is held and charged like your API client's [billed runs](https://docs.stellarium.sh/concepts/api-clients.md#how-a-billed-run-works): at most the client's maximum per run, within its monthly cap, at the node's listed price.

Your event mandates are listed under **Event contracts** on the **API clients** page of the [Stellarium Platform](https://platform.stellarium.sh). Revoke a mandate there to stop the node billing you for it. Revoking its API client stops it too. Deploying the contract again renews the mandate.

If the operator refuses a run because your balance is too low, or can't be reached, the node tries the event again later. A revoked mandate isn't retried.

## Chains of events

An event contract may emit events of its own. Each event carries its chain: the event types before it, and the contracts that emitted or consumed them. To stop loops:

* Chains are at most 4 events deep.
* A contract can't emit an event type already in its chain.
* A contract can't call a contract already in its chain, on its own node or another.
* An event contract can't emit the event type it consumes. That deploy is refused with `400`.

For example, contract A emits `Event1`, and event contract B consumes it. B can't emit `Event1`. If B emits `Event2`, event contract C may consume it, but C can't emit `Event1` or `Event2`, or call A or B.

## What to trust

* **Events are signed and checked.** A receiving node trusts the operator for which organization a node belongs to, and the publisher's key for the event itself.
* **A removed node can publish for a short while.** A node removed from its organization can still publish with its last membership token until it expires, within an hour.
* **Restricted delivery relies on announcements.** A publishing node trusts a peer's announcement of who deployed its event contracts when it decides whether to send it restricted events.
* **Billing stops runaway chains.** A node could start a fresh chain, but every event contract in a loop is paid for by the organization that deployed it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.