Track events and identify visitors

Send your own events with an HTML attribute, JavaScript or the API, and identify visitors so their activity comes together in one profile.

Pageviews and clicks on links to other sites are tracked automatically. For anything else, such as a signup, a button click or a plan upgrade, send your own event. And when someone logs in or leaves their email, tell ProjectHQ who they are, so their visits, events and purchases come together in one profile.

Everything on this page works through the tracking script you already have. If it isn't on your site yet, start with Install the tracking script.

Three ways to send an event

On the Analytics page, click Add goal in the Goals card to see ready-to-copy snippets for each method.

The Add goals panel with HTML selected: snippets to track a click, add properties and track how far people read

With an HTML attribute (no code)

Add data-phq-goal to anything people click. The event is sent when it's clicked:

<button data-phq-goal="cta_clicked">Start free trial</button>

Add details as extra attributes. data-phq-goal-plan becomes the property plan (up to 10 per element):

<button
  data-phq-goal="plan_selected"
  data-phq-goal-plan="pro"
  data-phq-goal-billing="yearly"
>Choose Pro</button>

To know whether people scrolled far enough to see something, use data-phq-view. It fires once per page load, when the section is half on screen:

<section data-phq-view="scroll_to_pricing">…</section>

With JavaScript

Call projecthq.goal() from your own code, with any properties you want to filter by later:

projecthq.goal('signup_completed', {
  plan: 'pro',
})

projecthq.track() sends a custom event in the same way. The difference is that goal() checks the name first and ignores names that don't follow the naming rules below, with a warning in the browser console.

From your server

For things that happen outside the browser, such as a payment webhook or a background job, send the event to the API with your project's API key:

curl -X POST https://api.projecthq.pro/v1/server-events \
  -H "X-API-Key: <your project key>" \
  -H "Content-Type: application/json" \
  -d '{
    "event_name": "subscription_started",
    "email": "customer@example.com",
    "properties": { "plan": "pro" }
  }'

Say who the event belongs to with one of visitor_id, contact_uuid, email or external_user_id (the ID you pass to identify(), below). ProjectHQ uses it to connect the event to that person's visits. Events it can't match are still recorded. For events that happened earlier, add occurred_at with the time in ISO 8601 format, for example 2026-10-06T14:30:00Z.

Your API key is in Analytics settings → Revenue & costs: choose From your server and click Show this project’s API key. Keep it secret: it belongs on your server, never in your website's code. If it ever leaks, a project owner can click Regenerate next to it — the old key stops working at once (see Track revenue and costs).

The API option in the Add goals panel: a curl request to the server-events endpoint with the X-API-Key header

Naming your events

Use lower case letters, numbers, _, - and :, up to 64 characters. Anything else is folded into that shape, so Signed Up! and signed_up count as the same goal. For common actions, prefer the standard names: signup_completed, trial_started, subscription_started and checkout_started. For a sale, send purchase with its amount, as shown in Track revenue and costs — any event that carries revenue counts as a sale, and purchase is the standard name for one.

Every event you send appears in the Goals card and the Completed goal filter, and can be a step in a funnel. See Goals and funnels.

Identify your visitors

Until you say otherwise, a visitor is anonymous and gets an alias such as "Teal Turtle". When someone logs in or signs up, call identify() with your own ID for them and anything you know:

projecthq.identify('user-123', {
  email: 'maya@fellroam.com',
  name: 'Maya Chen',
  company: 'Fellroam',
  plan: 'pro',
})

What happens next:

  • Their earlier anonymous visits are linked to them, and the Visitors list shows their name or email instead of an alias. Live activity shows the moment they identify themselves, and lists their actions under their name or email rather than the alias.
  • If you pass an email, they also become a contact in Contacts (or the existing contact is updated). name, company and avatar_url fill in the contact's details. plan, mrr, subscription_status, subscription_started_at and subscription_ends_at fill in their subscription. Any other trait is kept as a custom field on the contact. See Manage contacts.
  • The chat widget recognises them too, so your assistant knows who it's talking to.
The Live activity card: visitors listed by anonymous alias, with Golden Bee identifying as priya@kettleandcrema.com and completing signup_completed

When the person logs out, call projecthq.reset(). The next visitor on that browser then starts fresh instead of being mixed up with the previous one.

Track events and identify visitors — ProjectHQ Docs