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

# Events

> The runtime emits an event for every meaningful moment. Subscribe with onEvent and route them to your own analytics.

The runtime reports activity through a single `onEvent` callback on the provider. There is no built-in analytics destination you receive typed events and send them wherever you like (your warehouse, PostHog, the console).

```tsx theme={null}
<FirstflowProvider
  experiences={experiences}
  persistence={persistence}
  onEvent={(e) => analytics.track(e.type, e)}
>
```

## Event types

`onEvent` receives a `FirstflowEvent` for each moment in a run:

| Event                  | When                                                | Key fields                                  |
| ---------------------- | --------------------------------------------------- | ------------------------------------------- |
| `experience.shown`     | An experience starts                                | `experienceId`, `at`                        |
| `step.entered`         | A step becomes active                               | `experienceId`, `nodeId`, `at`              |
| `answer.submitted`     | A question is answered                              | `experienceId`, `questionId`, `value`, `at` |
| `action.triggered`     | A non-terminal `link`/`event`/`prompt` action fires | `experienceId`, `nodeId`, `action`, `at`    |
| `experience.completed` | A run reaches the end                               | `experienceId`, `at`                        |
| `experience.dismissed` | The user closes a run                               | `experienceId`, `at`                        |

## Reacting to actions

`action.triggered` is how you bridge in-widget actions to host behavior. A `prompt` or `event` action surfaces here so you can route it:

```tsx theme={null}
onEvent={(e) => {
  if (e.type === "action.triggered" && e.action.type === "prompt") {
    sendToLLM(e.action.prompt);
  }
  if (e.type === "action.triggered" && e.action.type === "event") {
    handleHostAction(e.action.name, e.action.data);
  }
}}
```

## Notes

* Events are delivered once (deduped against React's StrictMode double-invoke).
* `experience.shown`/`completed`/`dismissed` are the funnel signals; `step.entered` measures drop-off within a flow.
* For LLM-side telemetry (tokens, cost, traces), that lives in the [server SDK](/server/overview), not these client events.
