---
title: "Buyer analytics"
description: "Forward SeatPicker journey events to PostHog, Google Analytics, or your own telemetry system."
---

`SeatPicker.onAnalytics` is an optional callback that lets your application route
buyer journey events to any analytics system. SeatLayer does not require or
select the destination.

<Aside type="caution" title="Event catalog compatibility">
  The callback and current event catalog are released. New event names may be
  added over time, so treat unknown events as valid and avoid using this stream
  as a billing or transaction authority.
</Aside>

## Add a provider adapter

<Tabs>
  <TabItem label="PostHog">
    ```js
    const picker = new seatlayer.SeatPicker({
      container: "#picker",
      event: "ev_9f3a",
      publicKey: "pk_test_…",
      onAnalytics: (event, properties) => {
        posthog.capture(`seatlayer_${event}`, properties);
      },
    });
    ```
  </TabItem>
  <TabItem label="Google Analytics">
    ```js
    const picker = new seatlayer.SeatPicker({
      container: "#picker",
      event: "ev_9f3a",
      publicKey: "pk_test_…",
      onAnalytics: (event, properties) => {
        gtag("event", `seatlayer_${event}`, properties);
      },
    });
    ```
  </TabItem>
  <TabItem label="Custom endpoint">
    ```js
    const picker = new seatlayer.SeatPicker({
      container: "#picker",
      event: "ev_9f3a",
      publicKey: "pk_test_…",
      onAnalytics: (event, properties) => {
        navigator.sendBeacon(
          "/analytics",
          JSON.stringify({ event, properties }),
        );
      },
    });
    ```
  </TabItem>
</Tabs>

For a public Platform event, use a publishable key that matches the event mode
(`pk_test_…` for test or `pk_live_…` for live) and register the page's exact
Embed origin. The SDK obtains Public-only access directly and keeps the grant in
memory, so your server does not mint a buyer token when the chart loads. For a
login, presale, partner, or channel audience, replace `publicKey` with an async
`buyerAccessTokenProvider` backed by your authenticated server; an explicit
provider or token takes precedence.

Every property object includes `surface: "buyer"`. A throwing callback is caught
inside the picker and does not interrupt rendering, but your adapter should still
fail quietly and avoid synchronous network work.

## Current event catalog

| Event | Additional properties | Emitted when |
|---|---|---|
| `3d_opened` | `seats`, `hasHeights` | The interactive venue view opens |
| `3d_orbit_engaged` | — | The first real orbit/dolly gesture in this 3D mount occurs |
| `3d_seat_picked` | `seatId`, `sectionId?` | The buyer picks a seat from 3D |
| `3d_cinematic_played` | `durationMs`, `reducedMotion: false` | The seat approach animation completes |
| `3d_cinematic_skipped` | `reducedMotion: true` | Reduced-motion preference skips the animation |
| `3d_cinematic_cancelled` | — | The active cinematic is cancelled |
| `3d_panorama_opened` | — | A view-from-seat panorama opens |
| `3d_panorama_closed` | `viewMs` | The panorama closes |

All rows also include `surface: "buyer"`.

## Use a stable internal envelope

Decouple your product analytics from SDK event names by wrapping them:

```ts title="browser/seatlayer-analytics.ts"
type SeatLayerJourneyEvent = {
  schemaVersion: 1;
  source: "seatlayer";
  name: string;
  occurredAt: string;
  properties: Record<string, unknown>;
};

function forwardSeatLayerEvent(
  name: string,
  properties: Record<string, unknown>,
) {
  const event: SeatLayerJourneyEvent = {
    schemaVersion: 1,
    source: "seatlayer",
    name,
    occurredAt: new Date().toISOString(),
    properties,
  };

  productAnalytics.capture("buyer_seat_journey", event);
}
```

Your warehouse or dashboard can then map each incoming name without requiring SDK
callbacks throughout the codebase.

## Product questions these events can answer

- How many buyers discover and open the 3D venue view?
- Does interaction with 3D correlate with seat selection?
- How often is the cinematic skipped because of reduced-motion preference?
- Which sections receive the most 3D seat picks?
- Do buyers open a view-from-seat panorama, and how long do they inspect it?

These are journey signals, not proof that a hold, payment, or booking succeeded.
Use checkout responses, orders, and signed webhooks for transactional analytics.

## Privacy and resilience

- Do not attach buyer email, name, payment data, or unneeded identifiers.
- Confirm that seat and section identifiers fit your privacy policy.
- Respect the consent model already used by your application.
- Sample or batch in the analytics provider, not inside critical picker logic.
- Accept unknown names so a newly added event cannot break the adapter.
- Keep provider exceptions and rejected network calls away from the buyer UI.

## Verification checklist

- [ ] Events arrive in the intended test analytics project.
- [ ] The adapter prefixes or envelopes SDK event names.
- [ ] Unknown event names are accepted.
- [ ] No personal or payment data is added.
- [ ] Analytics consent is respected.
- [ ] A deliberately throwing adapter does not break the picker.
- [ ] Booking conversion comes from server/order data, not this callback.

Continue with the [3D buyer view](/buyer-sdk/3d-view) or
[custom application architecture](/integrations/custom-applications).