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

# Events

> React to what happens in a workspace: installs, your blocks and references, decisions, call recaps, new posts and replies, mentions of your app, and the conversations Reintersect finds in chat.

> Reintersect apps are in developer preview. Names and limits can still change before general
> release.

Events tell your app that something happened. Each event is a standalone piece you add to the app;
its payload is typed, and subscribing to an event declares the scope it needs.

```tsx src/events/onDecision.tsx theme={null}
import { defineEvent } from "@reintersect/sdk/app";
import { Paragraph } from "@reintersect/sdk/components";

export const OnDecision = defineEvent(
  "decision.published",
  { scopes: ["replies:write"] },
  async ({ decision, reintersect }) => {
    await reintersect.replies.create({
      conversationId: decision.conversationId,
      content: <Paragraph>Logged "{decision.title}" in the changelog.</Paragraph>,
    });
  },
);
```

Add it with `.addEvent(OnDecision)`. The app's manifest lists every event it handles, and the
consent screen shows them to admins as "Gets told when…".

Using Effect? See [Events](/effect/commands-and-events#events) in the Effect guide.

## The handler

`defineEvent(name, handler)` or `defineEvent(name, options, handler)`. The handler receives the event
payload's fields at the top level (here `decision`) plus:

| Field | Description |
| - | - |
| `reintersect` | A client with the scopes this event declared, plus the event's own read scope. |
| `actor` | The member behind the event, when there is one. |
| `env`, `config` | Declared environment and install config. |
| `waitUntil` | Keep work running after the handler returns. |

| Option | Description |
| - | - |
| `scopes` | Scopes the handler's client calls need. |
| `blocks` | Blocks the handler inserts or updates, as `{ Deploy }`, so the client is typed for them and the app must include them. |
| `env`, `config` | What the handler reads. |

Payloads are listed in the [event catalog](/events/catalog).

## Which installs receive an event

An install receives an event only when all of these hold:

1. The installed version's manifest lists the event, because the app added it.
2. The install holds the event's read scope: `decisions:read` for decisions, `calls:read` for call
   recaps, `conversations:read` for posts, replies, mentions, reference occurrences and the
   structure Reintersect finds in chat (`message.filed`, `conversation.updated`,
   `conversation.agreed`). Subscribing adds the scope for you.
3. Your app can reach the circle the event happened in:

| Install | Circles it hears from |
| - | - |
| Workspace install | Public circles, plus private circles your app was invited to: a member mentioned it there, ran one of its message actions there, or placed one of its blocks there. |
| Development install | Only circles the developer is an active member of. |
| Any install | Never circles connected to another workspace or a Slack channel. |

`app.mentioned` reaches a workspace install in any circle that isn't connected, because a mention is
an explicit invitation; it also invites the app to that circle from then on. Your app never receives
events about its own replies and blocks.

The same rule limits where your app can write. See
[Install and consent](/concepts/install-and-consent#reach).

## Delivery

Events travel on the lifecycle lane: a durable outbox that retries until your app answers.

* Every delivery is signed. See [Webhooks and security](/webhooks-and-security).
* Answer with any `2xx` within 15 seconds. Anything else, or a timeout, is retried with jittered
  backoff from one minute up to six hours, up to 20 attempts.
* Delivery is at least once. The SDK drops a repeated `webhook-id` it has already handled in the same
  process; make handlers safe to run twice anyway.
* Order isn't guaranteed. Every object in a payload carries a `version` that only increases: ignore
  an event whose version is older than one you already processed. `conversation.updated` and
  `conversation.agreed` carry the card's `revision` instead: ignore a delivery whose revision is lower
  than the last one you saw for that conversation. `message.filed` carries neither, so read the
  message's conversation again if order matters to you.
* Answering `410 Gone` pauses deliveries to that install. After three days of continuous failures,
  Reintersect also pauses deliveries and emails the app's owner (or the workspace owners). The developer resumes them from
  **Settings → Developer → Apps → your app → Deliveries**.

The deliveries page lists recent deliveries with their status, attempts and response, so you can
inspect failures.

## Testing

Send an event through your app with the [testing kit](/testing):

```ts theme={null}
const outcome = await host.emit("decision.published", {
  decision: {
    id: "dec_1",
    conversationId: "conv_1",
    circleId: "team_1",
    circleName: "Launch",
    title: "Ship v2.4 on Thursday",
    summary: "We ship v2.4 on Thursday after the review.",
    url: "https://app.reintersect.com/decisions/dec_1",
    version: 1,
    publishedAt: "2026-10-05T09:00:00Z",
  },
});

expect(outcome.handled).toBe(1);
```


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