# Events.

Two ways to deliver exposures and `dif.track()` metrics: send them to dif.sh Cloud, or send them wherever you want yourself. Pick one in `dif/config.yaml`; `dif build` compiles the choice into `dif/generated/events.ts`.

## Two modes

The `events.mode` field in `dif/config.yaml` is either `cloud` or `custom`. `dif init` asks which one on an interactive terminal, and defaults to cloud otherwise.

| mode | what it does |
| --- | --- |
| `cloud` | The default. The SDK posts exposures to `<apiUrl>/v1/exposure` and `dif.track()` metrics to `<apiUrl>/v1/track`, authenticated with a publishable key. |
| `custom` | The SDK calls handlers you export from `dif/events/exposure.ts` and `dif/events/track.ts`. Forward to Amplitude, Mixpanel, Segment, a webhook — whatever you like. |

## Cloud mode Cloud

The built-in path. Connect an existing workspace with [`dif connect`](https://dif.sh/docs/cli/#connect):

```
$dif connect --key dif_pk_live_…
✓ connected dif/config.yaml to dif.sh Cloud
  next: run dif build to regenerate the client with your key
```

That writes the key into the `events:` block and forces cloud mode — safe to commit, it's the publishable key, never the secret server key:

```
events:
  mode: cloud
  url: https://cloud.dif.sh
  key: dif_pk_live_…
```

A fresh workspace can skip the two-step: `dif init --key dif_pk_live_…` writes the same block. Either way, `dif build` bakes it into `dif/generated/events.ts`; pass that straight to `dif.init()`:

```
import { dif } from "@dif.sh/sdk";
import { events } from "@/dif/generated/events";

dif.init({ events });
```

The publishable key rides on the generated `events` object once you've connected — no environment variable needed. An explicit top-level `publishableKey` passed to `dif.init()` overrides it, handy for swapping keys per environment. Without a publishable key anywhere, cloud events are dropped with a one-time console warning — analytics never block a render. Server-side tracking via [the server SDK](https://dif.sh/docs/sdk/#server) is always cloud.

## Custom mode

Mirrors [audience resolvers](https://dif.sh/docs/format/#audiences): you export a default function, dif calls it. `exposure` fires once per `(experiment, user)` per session at render time; `track` fires on every `dif.track(...)` call. `dif init --events custom` scaffolds both files.

```
// dif/events/exposure.ts
import type { ExposureEvent } from "@dif.sh/sdk";

export default function exposure(event: ExposureEvent): void {
  amplitude.track("dif.exposure", {
    experiment: event.experiment,
    variant: event.variant,
  });
}
```

```
// dif/events/track.ts
import type { MetricEvent } from "@dif.sh/sdk";

export default function track(event: MetricEvent): void {
  mixpanel.track(event.metric, { value: event.value, ...event.props });
}
```

Keep handlers non-throwing — analytics must never crash a render. No publishable key is needed in custom mode; delivery is entirely yours.

## The generated module

`dif build` writes `dif/generated/events.ts` from your config. In custom mode it imports your handlers:

```
// dif/generated/events.ts — generated by dif build
import exposure from "../events/exposure";
import track from "../events/track";
export const events = { mode: "custom", exposure, track } as const;
```

In cloud mode it records the URL and, once connected, the key:

```
export const events = { mode: "cloud", apiUrl: "https://cloud.dif.sh", publishableKey: "dif_pk_live_…" } as const;
```

Either way the app does the same thing: import `events` and pass it to `dif.init()`. `dif/generated/` is gitignored by default — it's a build artifact, not source.

## Migrating from exposure:

Earlier versions configured delivery with an `exposure:` block. That key is now ignored — the workspace defaults to cloud — and `dif validate` flags it as `W003`. Replace it with an `events:` block: `mode: cloud` to keep using dif.sh Cloud, or `mode: custom` to move your own delivery into `dif/events/exposure.ts` and `dif/events/track.ts`.
