Some goals happen where nobody is on the site: a subscription that renews on a schedule, an order confirmed by a webhook, a trial that converts overnight. The script cannot see any of them, so post them yourself.
POST https://truestat.io/api/events
Content-Type: application/json
{
"site_key": "ts_your_site_key",
"event_type": "goal",
"name": "subscription_renewed",
"path": "/billing",
"visitor_id": "the ts_vid cookie value",
"metadata": { "plan": "pro" }
}A 202 means it was accepted.
Any HTTP client works. curl, fetch, axios, requests — the goal is
counted whatever user agent your server sends. Pageviews from those same
clients are still treated as automated traffic, because a pageview from a
script is not a person reading a page; a goal from your server is.
The fields
| Field | Required | Notes |
site_key | yes | The same key as in your script tag |
event_type | yes | "goal" |
name | yes | Lowercase letters, numbers, _ - :, up to 64 characters |
visitor_id | no | The visitor's ts_vid cookie — see below |
path | no | Where the goal happened, for the pages report |
metadata | no | Properties to break the goal down by |
The visitor id ties it to the visit
visitor_id is what connects the goal to the rest of that person's session —
the channel that brought them, the pages they saw. Read it from the ts_vid
cookie on your own request and pass it through.
Without it the goal is still recorded, but it stands alone: it counts on the card and contributes nothing to any breakdown, because there is no visit to attach it to.
This endpoint requires the cookie's exact value. ts_vid holds a UUID and
/api/events rejects anything that is not one, so pass it through unchanged —
do not truncate, re-encode or hash it. (The
money-value route is more relaxed and
takes any string up to 64 characters.)
No prior pageview is needed
A goal from a server for a visitor we have never seen is accepted. That is what makes server-only tracking possible — and it also means the id is taken on trust.
On the site key
The same key is in your page's script tag, so it is not a secret and this endpoint does not treat it as one. Anyone who views your source can post events to your site.
What protects you is the rate limit and the fact that nothing here can read your data or change your account — a forged event is noise in your own numbers, not a breach.
If that matters for a particular goal — a signup count you report to investors, say — send it from your server and compare against your own database. We would rather tell you this than let you assume the key is doing more work than it is.
This route cannot carry a value
value_cents is refused here, for the reason above: the key is public, so an
amount it could send is an amount anyone reading your page could send. Money
goes through the API-key route instead.
Naming
Some names are reserved for events the billing integration writes — sending one yourself would merge two sources into one row. See reserved names.