---
title: Events
description: "A contract names every event a session can hold, with a schema for each payload."
---

## Declare the events

```ts contracts.ts
import { z } from 'zod'
import * as a2 from 'experimental-a2'

export const orders = a2.contract({
  name: 'orders',
  events: {
    created: z.object({ shopId: z.string(), items: z.array(z.string()) }),
    'shop.notified': z.object({}),
    'shop.started': z.object({}),
    'driver.notified': z.object({ driverId: z.string() }),
    expired: z.object({}),
  },
})
```

A **contract** defines what can happen. A [session](/contract/sessions) is one
instance of it.

`name` identifies the contract everywhere: in storage, in queue messages, in
logs. It keeps an app with several contracts unambiguous.

`events` is the full vocabulary. Each key is an event name; each value is a
schema for its payload. These docs use Zod, but any
[Standard Schema](https://standardschema.dev) library works: Valibot, ArkType,
your own.

The contract is a plain, importable, **isomorphic** value. The server
implements it with [handlers](/server/handlers), [reducers](/contract/state)
derive the current view from it, and the browser types its pushes off it.

## Validation

Every append is validated against the contract. A bad payload throws
`INVALID_PAYLOAD` before anything is written; an unknown type throws
`UNKNOWN_EVENT_TYPE`. The log stores the validated output, with defaults
applied and transforms run.

## Presence

A contract can also declare `presence`: ephemeral participant state (cursors,
typing, live progress) that is replicated but never stored in the log. See
[Presence](/client/presence).

:::tip
Events are facts, so name them in past tense: `created`, `expired`,
`shop.notified`. A name that reads like a command (`notifyShop`) describes
what a handler should do, not what happened. Dots are a naming convention, not
a namespace.
:::
