airhop docs

Send product events

Identify your users and track what they do, with the Airhop SDK, the ingest batch API, or Segment.

Product events are who your users are and what they do in your app. They power activation milestones, the health score, and lifecycle playbooks. The shape follows the Segment spec (identify, track, page, group), so if you already use Segment you can connect Airhop without code.

Your write key

Events are authenticated with your write key. It starts with wk_ and is write-only, so it is safe in client-side code. You will find it on the Install onboarding step, and later behind the Install snippet button on the Activation page.

There are two distinct keys:

  • The widget public key (pk_...) identifies your widget. See Install the widget.
  • The events write key (wk_...) authorizes event ingestion. It is the one used on this page.

Option A: the browser SDK

Add the SDK script the same way as the widget, then call identify and track:

<script src="https://app.airhop.ai/airhop.js" data-key="wk_your_write_key" async></script>
<script>
  // Right after a user logs in or signs up:
  airhop.identify(USER_ID, { email: USER_EMAIL, name: USER_NAME, plan: USER_PLAN });

  // When they do something meaningful:
  airhop.track('project_created', { projectId: '...' });
</script>

The SDK exposes four methods on window.airhop:

  • airhop.identify(userId, traits): tie events to a known user and set traits like email, name, and plan.
  • airhop.track(event, properties): record an action. Use clear, stable snake_case names.
  • airhop.page(name, properties): record a page view.
  • airhop.reset(): clear the current user (for example, on logout).

The SDK keeps a persistent anonymous id in localStorage and attaches it to every call, so events fired before a user logs in are merged onto their profile once you call identify. Calls are batched and flushed shortly after (and on page hide), sent to the ingest API with your write key.

Pick the few actions that signal a user is getting value. Those become your activation milestones. Keep event names stable; never rename an event already in use, add a new one instead.

Option B: the ingest batch API

For server-side events, or any non-browser source, POST directly to the ingest API. Authenticate with your write key as a Bearer token.

curl https://app.airhop.ai/ingest/v1/batch \
  -H "Authorization: Bearer wk_your_write_key" \
  -H "Content-Type: application/json" \
  -d '{
    "batch": [
      {
        "type": "identify",
        "userId": "user_123",
        "traits": { "email": "jo@acme.com", "name": "Jo Diaz", "plan": "pro" }
      },
      {
        "type": "track",
        "userId": "user_123",
        "event": "project_created",
        "properties": { "projectId": "p_1" }
      }
    ]
  }'

The API accepts the Segment call shape. The endpoints under /ingest/v1 are:

EndpointUse
POST /ingest/v1/identifyA single identify call.
POST /ingest/v1/trackA single track call.
POST /ingest/v1/pageA single page call.
POST /ingest/v1/groupA single group call (link a user to an account).
POST /ingest/v1/batchMany calls at once, as { "batch": [ ... ] }.

A single call can also be posted to its typed endpoint without a type field. The batch endpoint takes any mix of call types. Ingestion is processed asynchronously, so the API returns quickly with the number of calls received.

Authentication

Use the write key as a Bearer token:

Authorization: Bearer wk_your_write_key

Two other forms are accepted for tooling that needs them: HTTP Basic auth with the key as the username (empty password), or a writeKey field in the body. (A Segment Webhooks (Actions) destination uses the Bearer header above — see A Segment destination below.)

From a server you control, prefer your secret key (sk_...) over the write key. Both ingest events, but only secret-key traffic is trusted — a write key cannot overwrite a contact's existing email, and any playbook it triggers drafts for approval instead of sending. See the API reference for the full trust model.

Limits & responses. Up to 5,000 calls per batch (413 beyond that), 6,000 requests per minute per key, and 200 { "received": N } on success. Set a stable messageId per call to make retries safe. The full call-object fields, response codes, and backfill flags are in the API reference.

Backfilling existing users

Already have users and history when you adopt Airhop? Replay their past identify and track calls with original timestamps so your funnel, health, and alerts are accurate from day one. See Backfill historical data.

A Segment destination

If you already send events through Segment, you do not need to write any code. Add Airhop as a destination using Webhooks (Actions), connect it to your source, and add a Send mapping pointed at:

https://app.airhop.ai/ingest/v1/segment

Configure the Send mapping like this:

  1. Method POST, URL the endpoint above (type it and choose "Create as string" — Segment's URL field takes a string constant this way).
  2. Under Headers, add Authorization with the value Bearer sk_your_secret_key. Use your secret key (sk_...), not the write key — a Segment webhook is server-to-server, and only secret-key traffic is trusted (it can enrich contacts and auto-run playbooks). Do not use the destination's "Shared Secret" setting; that produces an HMAC X-Signature header Airhop doesn't check.
  3. Set the trigger to fire on Event Type is track (add a second condition, or a second mapping, for identify).
  4. Leave Segment's batching off — Airhop expects one event per request. (The { "batch": [ ... ] } form is for your own backend, which you control.)

Segment then forwards your identify and track calls to Airhop unchanged, and they flow into the same pipeline as the native SDK.

PostHog and Intercom can be wired up the same way, against /ingest/v1/posthog and /ingest/v1/intercom; Airhop normalizes each source's event shape into the same canonical events.

Grouping users into accounts

A group call links a user to an account (company), which is authoritative over Airhop's automatic domain grouping:

airhop.identify('user_123', { email: 'jo@acme.com' });
// then, with the account id and any account traits:
// (sent server-side or via a group call)
{
  "type": "group",
  "userId": "user_123",
  "groupId": "acme_inc",
  "traits": { "name": "Acme Inc" }
}

See Accounts and health for how accounts form and what feeds the health score.

On this page