Copy as markdown[View .md](https://docs.funnelfizz.com/concepts/server-events "View the raw markdown for this page")[Open in Claude](https://claude.ai/new?q=Read%20https%3A%2F%2Fdocs.funnelfizz.com%2Fconcepts%2Fserver-events.md%20and%20help%20me%20with%20this%20FunnelFizz%20topic%3A%20Server-side%20events "Open this page in Claude with context")[Open in ChatGPT](https://chat.openai.com/?q=Read%20https%3A%2F%2Fdocs.funnelfizz.com%2Fconcepts%2Fserver-events.md%20and%20help%20me%20with%20this%20FunnelFizz%20topic%3A%20Server-side%20events "Open this page in ChatGPT with context")

# Server-side events

The standard tracking snippet captures events from the browser. Some events only happen on your server — a Stripe webhook firing, a signup callback, an internal user action — and those need a server-side path.

## What it does[​](#what-it-does "Direct link to What it does")

`POST /api/tracking/server-events` records events that:

1. Land in `TrackingEvent` (same place as client events).
2. **Enrich the user profile** — same path as client tracking, so the event also shows on the profile timeline (`ProfileEvent`), advances `ProfileFunnelLink.currentStage` if applicable, and updates engagement counters.
3. Are workspace-scoped — events written through this endpoint are bound to the authenticated session's workspace and a tracking site you own.

## Auth model[​](#auth-model "Direct link to Auth model")

This endpoint takes the **session cookie** of the logged-in workspace user — not an API key. That makes it safe for first-party use (your dashboard or admin tools running under the same session), but it is **not** intended for true server-to-server calls from a backend you don't control. For backend integrations on a different host, the standard pattern is:

* Forward the visitor cookie (`_fn_vid`) to your backend in the original request.
* Pass the visitor ID through Stripe `client_reference_id` (see [install-tracking](https://docs.funnelfizz.com/getting-started/install-tracking.md#dont-have-an-email-until-checkout)).
* Or use the MCP `write:custom_events` mutation tool with an API key.

## Request[​](#request "Direct link to Request")

```
POST /api/tracking/server-events?workspaceId=ws_123

Content-Type: application/json

Cookie: <session-cookie>



{

  "siteId": "site_456",

  "events": [

    {

      "type": "signup",

      "visitorId": "<from _fn_vid cookie>",

      "email": "sam@example.com",

      "path": "/api/signup",

      "props": { "plan": "pro" }

    }

  ]

}
```

## Constraints[​](#constraints "Direct link to Constraints")

* Max 50 events per request.
* Max 8 KB per event `props`.
* Rate-limited at 600/hr per session user.
* `userId` is forced to the session user's ID — payload-supplied user IDs other than yours are rejected.
* The `siteId` must belong to the workspace identified by `?workspaceId=`.

## What gets recorded[​](#what-gets-recorded "Direct link to What gets recorded")

For each event:

* A row in `TrackingEvent` with the channel categorized from `referrer` / `utm`.
* A row in `ProfileEvent` (the profile timeline) tagged with `profileId`, `workspaceId`, and `funnelId`.
* Identifier rows in `ProfileIdentifier` if the event carries `email` or `userId` — feeding the cross-device merge logic.
* Stage advance to `CONSIDERATION` on `signup` / `identify`, or to `TRIAL` on `trial_signup` / `trial_start` (see [tracking concepts](https://docs.funnelfizz.com/concepts/tracking.md)).

## When to use[​](#when-to-use "Direct link to When to use")

* **Server-side signups** that bypass a client-side `funnelfizz('event', 'signup')` call.
* **Stripe webhook side-effects** that need to mirror onto the profile timeline (when `client_reference_id` isn't enough).
* **App-internal events** like "user invited a teammate" that you want to track without exposing the visitor ID to the browser.
