Track revenue and costs
Report sales and refunds from your site or server, record per-customer costs and import campaign spend to see which traffic makes money.
Report your sales to ProjectHQ and Analytics shows which channels, sources, campaigns and landing pages actually bring in money, not just visitors. You can also record what each customer costs you to serve and what you spend on ad campaigns.
There's nothing to switch on. The first sale you report fills in the Revenue card on the Analytics page.
Where to find it
- On the Analytics page, the Revenue card is in the Conversions section. Until a sale arrives, it offers Set up revenue tracking.
- The setup lives in Analytics settings → Revenue & costs.
Report a sale
There are two ways in, and they record the same thing. Pick the one that matches where the payment finishes. Any event that carries revenue counts as a sale; purchase is the standard name for one. Sales sent under the older name purchase_completed still count.
In the browser
If the payment finishes on a page of your site, such as an order confirmation page, send an event with the amount from that page:
projecthq.track('purchase', {
revenue: 49.00,
currency: 'USD',
order_id: 'order-123',
})
The tracker already knows which visit this is, so the sale is credited to the source that brought the visitor. There's nothing else to pass.
From your server
If the payment finishes in a webhook (a Stripe subscription renewal never touches a browser), send it from your server. Choose From your server and click Show this project’s API key to get the key for the X-API-Key header:
await fetch('https://api.projecthq.pro/v1/server-events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'YOUR_API_KEY',
},
body: JSON.stringify({
event_name: 'purchase',
revenue: 49.00,
currency: 'USD',
order_id: 'order-123',
email: 'customer@example.com',
}),
})
Your server has no visit to read, so it has to say who bought. Send their email, or the external_user_id you passed to identify() (see Track events and identify visitors), and ProjectHQ matches the sale to the person who browsed. Sales it can't match are still recorded, so your total still adds up with your payment provider; they just aren't credited to a source.
Replace the API key
If the key has leaked, a project owner can replace it: click Regenerate next to Show this project’s API key and confirm with Regenerate key. Only owners see the button.
Do this with care. The current key stops working immediately, and every server that reports sales, costs or crawler visits with it is rejected until you give it the new key. The new key is shown as soon as it's created.
Rules that keep the numbers right
- Send an
order_id. The same ID twice is one sale, so a webhook that fires again can't double your revenue. - Refunds: send
event_name: 'refund'with a negativerevenueand the sameorder_id. The amount is taken off on the day of the refund, without rewriting the day of the sale. - Currency: if you leave it out, the project's default currency is used. You can change it in Analytics settings → Goals → Edit goals → Project defaults.
Read the Revenue card
- Switch between Channels, Sources, Campaigns and Landing pages.
- Last touch credits the visit the purchase happened in. First touch credits the source that first brought that customer to your site. Every sale is counted under both, so you can compare.
- CR is sales per visit, Sales is the number of sales, and Revenue is the amount. The Total line sums the table.
If you've set business goals, the Revenue card only shows when your main goal is a Sales goal. See Goals and funnels.
Record what a customer costs
If you pay for something each time a customer uses your product, such as an AI call, an SMS or a payment fee, send a cost event from your server after each billable action. Click Show cost event example for the full snippet. A cost event needs:
cost: this action's cost, not a running total. Send a negative cost to correct a charge.cost_category, for exampleaiorpayment_fee.event_id: a unique ID. Reuse it when you retry, so the cost isn't counted twice.- Who it's for:
external_user_id(as inidentify()),emailorvisitor_id.
Costs show up on the person's profile next to their revenue: open a visitor from the Analytics page to see Revenue, Recorded costs and Balance, with All time finances or the Selected period.
Import campaign spend
To see what your ad campaigns cost per conversion, import your daily spend as a CSV file under Campaign spend:
- Prepare a file with the columns
date,source,campaign,currency,cost, one row per campaign per day. Before your first import, Download an example CSV gives you a ready-made file. The file must be smaller than 1 MB. - Click Import costs and choose your file.
- Check the preview and click Confirm import. A row for the same day, source, campaign and currency replaces the spend recorded before; other days stay as they are.
The table shows the Spend per campaign. Once you have a main business goal, it also shows Conversions, Cost / conversion and ROAS (return on ad spend). Conversions are matched by the source and campaign of the visit, so use the same names you put in your UTM tags. Spend is tracked per campaign, not assigned to individual people.