# Server-side events

Sending events from your backend or over HTTP, and tying them to a channel.

## Can I send events from my backend instead of the browser?

Yes, alongside a client install rather than instead of one. The server entry point sends events into a project that already has the SDK on a client surface. A project with no client install at all is not supported, so your backend cannot be the only place events come from.

To use it: import from @heycatch/sdk/server, call init once at module scope rather than per request, and await each call.

How that entry behaves:
1. Every event is sent immediately. There is no batching, so nothing sits in a buffer a serverless function will never flush.
2. No cookies and no DOM are involved.
3. It never throws. A failure logs and resolves, so analytics cannot break your request.
4. Property values are string, number, boolean or null.
5. Every call needs a stable internal user id, the same one your client uses, or the two sides do not join into one person.
6. It works from CommonJS with require, and geo lookup is off by default on this entry.

Channel attribution and short links come from the client alone, because both are built from pageviews and the referrer. That is the second reason the client install has to be there.

## I send events over plain HTTP. Does heycatch_project_key replace PostHog's api_key, or do I need both?

You need both, and they do different jobs.

api_key is required: the ingest rejects a request without it. Its value is our public ingest key, phc_oiDt6uXiBiEA2aT43SMzMAFE9D4gMVkRP3BtvYRsmHqe. It is the same for every customer and ships inside our SDK, so it is not a secret you need to guard.

heycatch_project_key is what assigns the event to your project. Put your own project key, the one that starts with hck_pk_, in the properties of every event, together with "$groups": {"project": "hck_pk_..."}. Without those two the event is accepted but belongs to nobody, and your dashboard stays empty. Your project key sits on the Analytics, Install screen until your first install is confirmed; once the install is confirmed, the key sits behind the gear icon in the top right of the Product page.

The rest is PostHog's standard capture format. POST JSON to https://in.heycatch.ai/capture/, or to /batch/ for several events at once, with api_key, event, distinct_id and properties. distinct_id is your own stable internal user id, the same one your client passes when it identifies the user. A request with a field missing comes back as a 4xx with a plain-text reason, for example "event submitted without an api_key".

It is plain HTTP, so it works from any backend language. From a backend, it is an addition to a client install rather than a replacement for one: a project with no client install at all is not supported. A mobile app that already runs PostHog for its own analytics is different: it sends its in-app events to us this way from the app itself, and that is its app install, see the [Mobile apps](/analytics/mobile-apps/) article.

## How does a sign-up or payment sent from my backend get tied to the channel that brought the user? Do I need an alias call?

No alias call is needed, and our SDK does not have one. What joins the two sides is a shared user id.

1. On the client, identify the person with your own stable internal user id as soon as they sign in: setIdentity in our SDK, or identify if you use a PostHog library pointed at our endpoint. Never an email or a session token.
2. From your backend, send every event with that same id, as the user id in our server entry or as distinct_id over plain HTTP.

The visits the person made before signing in, including the one that carried the channel, belong to the same person once they are identified. So a sign-up or payment your backend reports later is counted against that channel.

Two limits to plan around.
1. The channel exists only on the web. It is read from a pageview on your own site, from the campaign tag on the link and the referrer, so a backend on its own, or a native app on its own, gets no channel attribution.
2. If someone finds you on the web but signs up only inside your mobile app, the web visitor and the app user are two separate people to us and nothing joins them. Store-level numbers, impressions and installs, stay in App Store Connect and Play Console.

Name the sign-up event exactly signup_completed: the sign-up counter reads that name and nothing else.
