DocsNamed actions

Docs · Reference

Named actions with track()

Report actions such as adding to cart, booking or signing up by name, from the code that performs them.

Page views, searches, form posts, and cart and checkout requests show up as steps on their own, read from each request's method, path and status. Some actions don't look like anything from the outside: a booking API call, a signup behind a generic form post. track() reports them by name, from the code that performs them, so a visit reads “Added to cart: Blue T-shirt” instead of “Submitted a form”.

Naming actions

  • name says what happened, in a few words: “Added to cart”, “Booked”, “Signed up”. Up to 100 characters. Use the same name every time the same thing happens.
  • target is optional and says what it was done to: a product, a service, a plan. Up to 200 characters.
  • Never put personal data such as names or email addresses in either. For people (as opposed to agents), only the action's name is kept; the target is dropped, since it's your own free text.

Add track()

The Next.js and Express collectors come with a track() helper:

Next.js: Add this file, then call track() wherever your app adds to cart, books or signs someone up.

lib/ethogram.ts
import { after } from "next/server";import { headers } from "next/headers";// In a server action or route handler: await track("Added to cart", product.name)export async function track(name: string, target?: string) {  const h = await headers();  const ip = h.get("cf-connecting-ip") ?? h.get("x-real-ip") ?? h.get("x-forwarded-for")?.split(",").at(-1)?.trim();  // Who did it. Signatures aren't checked on actions (the page's own request carries them), so they needn't go.  const sent = Object.fromEntries(["user-agent", "signature-agent"].flatMap((k) => (h.get(k) ? [[k, h.get(k)]] : [])));  after(() =>    fetch("https://ethogram.si/api/e", {      method: "POST",      headers: { authorization: `Bearer ${process.env.ETHOGRAM_KEY}`, "content-type": "application/json" },      body: JSON.stringify({        collector: "0.12",        events: [{ url: h.get("referer") ?? `https://${h.get("host")}/`, method: "POST", time: Date.now(), ip, headers: sent, action: { name, target } }],      }),    }).catch(() => {}),  );}

Express: Call track() in the route that does it. It reuses report() from the middleware above.

server.js
// In the route that does it: track(req, "Added to cart", product.name)// Who did it. Signatures aren't checked on actions (the page's own request carries them), so they needn't go.const track = (req, name, target) =>  report(req, { time: Date.now(), headers: { "user-agent": req.get("user-agent"), "signature-agent": req.get("signature-agent") }, action: { name, target } });

With any other server, add an action field to an event sent to the HTTP API, with the page the action happened on as its url:

Event field
"action": { "name": "Added to cart", "target": "Blue T-shirt" }

Examples: cart, booking, signup

In Next.js, from a server action or route handler:

Next.js
// Cart: in the server action that adds the item.await track("Added to cart", product.name);// Booking: once the slot is reserved.await track("Booked", `${service.name}, ${slot.day}`);// Signup: once the account exists. Name what was chosen, never who signed up.await track("Signed up", plan.name);

In Express, from the route that does it:

Express
track(req, "Added to cart", product.name);track(req, "Booked", `${service.name}, ${slot.day}`);track(req, "Signed up", plan.name);

How actions show up

  • Each action is a step of its own in the visit's timeline, labelled with its name and target.
  • A tracked action that follows the form post that triggered it within 10 seconds replaces that post, so the step shows once.
  • An action isn't a request of its own, so it doesn't add to the visit's request count.
  • A visit that takes an action, or opens checkout, and then never reaches a confirmation page counts as left before finishing. Make an action a goal and reaching it counts as completed. Enough of those from one agent raise an abandoned insight.
  • Signatures aren't checked on actions (the page's own request carries them), which is why track() sends only the user-agent and signature-agent headers: enough to say who did it.