Swetrix

Events API reference

With Swetrix you can programmatically record page views or custom events manually. Typically this API is used when doing server-side tracking or in mobile/native applications.

We already provide several integrations and a script for this, but if for some reason none of these options work for you, you can use the Events API instead.

Concepts

Unique visitors tracking

For the unique (and live) visitor functionality to work properly, each request must have the User-Agent header set, and — when sent from a backend server or proxy — the X-Forwarded-For header with the visitor's IP address. If you don't set these headers, the API will still work, but the unique visitor count will be incorrect.

The API relies on the client's IP address and user agent to create a temporary session (a salted hash that is permanently removed from our database 30 minutes after the last interaction or at 12:00 AM UTC, whichever comes first). We never store any visitor identifiable information in our database.

Request headers

User-Agent

This header is used to determine the visitor's browser, operating system and device type. It's also used to determine whether or not the visitor is a bot. We also use this header to create a temporary session for the visitor.

X-Forwarded-For

Used to explicitly set the IP address of the visitor. If not set, the remote IP of the sender is used automatically. Depending on your use-case:

  1. If sending the event directly from your visitor's device, this header does not need to be set.
  2. If sending the event from a backend server or a proxy, make sure to set this header to the visitor's real IP address.

If you forward a server, hosting provider or CDN IP address instead of the actual visitor IP, unique and live visitor counts will be incorrect and geolocation will reflect your server's location. On projects with bot protection set to Strict, such events are dropped entirely as datacenter traffic — the API still responds with a success status, but the event is not recorded. You can confirm drops in the Blocked traffic report under Project settings → Shields.

The IP address is never stored in raw form — it's used to calculate the temporary visitor session described above and to fill the geolocation reports (country, region, city).

If the header contains a comma-separated list (as it does when the request passed through a chain of proxies), the first valid IP address from the list is used. Both IPv4 and IPv6 addresses are supported. More information about the header format can be found on MDN.

X-Client-IP-Address (legacy)

Older Swetrix integrations used the custom X-Client-IP-Address header for the same purpose. It's still honoured and takes precedence over X-Forwarded-For, so existing setups keep working — but it's not recommended for new integrations; use the standard X-Forwarded-For header instead.

Content-Type

Must be set to application/json for all requests.

Pageview event structure

Pageview payload

NameTypeRequiredDescription
pidstringtrueA project ID to record the pageview event for
tzstringfalseVisitor's timezone (used as a backup in case IP geolocation fails). I.e. if it's set to Europe/Kiev and IP geolocation fails, we will set the country of this entry to Ukraine)
pgstringfalseA page to record the pageview event for (e.g. /home). All our scripts send the pg string with a slash (/) at the beginning, it's not a requirement but it's best to do the same so the data would be consistent when used together with our official scripts
lcstringfalseA locale of the user (e.g. en-US or uk-UA)
refstringfalseA referrer URL (e.g. https://example.com/)
sostringfalseA source of the pageview (e.g. ref, source or utm_source GET parameter)
mestringfalseA medium of the pageview (e.g. utm_medium GET parameter)
castringfalseA campaign of the pageview (e.g. utm_campaign GET parameter)
uniquebooleanfalseIf set to true, only unique visits will be saved
perfobjectfalseAn object with performance metrics related to the page load. See Performance metrics for more details

Performance metrics payload

This section describes the structure of the perf object which is used to record performance metrics related to the page loading.

All of the values are numbers in milliseconds.

NameTypeRequiredDescription
dnsnumberfalseDNS Resolution time
tlsnumberfalseTLS handshake time
connnumberfalseConnection time
responsenumberfalseResponse Time (Download)
rendernumberfalseBrowser rendering the HTML page time
dom_loadnumberfalseDOM loading timing
page_loadnumberfalsePage load timing
ttfbnumberfalseTime to first byte

Custom event structure

NameTypeRequiredDescription
pidstringtrueA project ID to record the pageview event for
evstringtrueAn event identifier you want to track. Can contain any characters (including spaces, unicode, etc.) with a maximum length of 256 characters.
uniquebooleanfalseIf set to true, only 1 custom event will be saved per session
pgstringfalseA page that user sent data from (e.g. /home)
lcstringfalseA locale of the user (e.g. en-US or uk-UA)
refstringfalseA referrer URL (e.g. https://example.com/)
sostringfalseA source of the pageview (e.g. ref, source or utm_source GET parameter)
mestringfalseA medium of the pageview (e.g. utm_medium GET parameter)
castringfalseA campaign of the pageview (e.g. utm_campaign GET parameter)
metaobjectfalseA key / value pair of custom properties for your event (e.g. if your event is signup, you can include meta like { affiliate: 'Yes', currency: 'GBP' }). The values of the object must be a primitive type (string, number, boolean, null) which will be converted to a string, the maximum number of keys allowed is 100 and the total length of the keys and values combined must be less than 2000 characters.

Error event structure

NameTypeRequiredDescription
pidstringtrueA project ID to record the pageview event for
namestringtrueError name (e.g. ParseError); Max length is 200
messagestringfalseError message (e.g. Malformed input); Max length is 2000
linenonumberfalseLine number
colnonumberfalseColumn number
stackTracestringfalseStack trace; Max length is 7500
filenamenumberfalseError message (e.g. https://example.com/assets/convert.js); Max length is 1000
tzstringfalseVisitor's timezone (used as a backup in case IP geolocation fails). I.e. if it's set to Europe/Kiev and IP geolocation fails, we will set the country of this entry to Ukraine)
pgstringfalseA page that user sent data from (e.g. /home)
lcstringfalseA locale of the user (e.g. en-US or uk-UA)
metaobjectfalseA key / value pair of custom properties for your event, for example { level: 'Critical' }. The values of the object must be a primitive type (string, number, boolean, null) which will be converted to a string, the maximum number of keys allowed is 100 and the total length of the keys and values combined must be less than 2000 characters.

Heartbeat event structure

NameTypeRequiredDescription
pidstringtrueA project ID to record the heartbeat event for

Identify event structure

NameTypeRequiredDescription
pidstringtrueA project ID to identify the visitor for
profileIdstringtrueA unique, stable identifier of the user (e.g. an internal user ID), max 256 characters. It's stored as provided, behind a usr_ prefix.
traitsobjectfalseA key / value pair of metadata describing the user, for example { email: 'john@example.com', plan: 'premium' }. Values must be a primitive type (string, number, boolean, null) which will be converted to a string; null removes a trait. Max 50 keys, 128 characters per key, and 2000 characters for all keys and values combined.

Revenue event structure

Unlike other /log/* endpoints, POST /log/revenue requires authentication with your personal API key (X-Api-Key header) — the same key used by the Statistics API. Revenue is financial data, so it must be sent from your server, never from the browser. See the Revenue Tracking guide for the full setup walkthrough.

NameTypeRequiredDescription
pidstringtrueA project ID to record the revenue event for
type'sale' | 'refund' | 'subscription'trueTransaction type. Use refund for refunds — the amount sign is handled automatically.
amountnumbertrueAmount in major units (e.g. 49.99 for €49.99). Always send a positive number; the sign is flipped server-side for refunds.
currencystringtrueISO 4217 currency code (e.g. USD, EUR). Converted to your project's reporting currency automatically.
transactionIdstringfalseStable unique identifier. Re-sending the same transactionId replaces the previous version (idempotent). If omitted, a UUID is generated.
productIdstringfalseOptional SKU / product identifier.
productNamestringfalseOptional human-readable product name shown in the transactions table.
profileIdstringfalseProfile ID for revenue attribution. Same value you'd pass to swetrix.init({ profileId }).
sessionIdstringfalseSession ID for attributing revenue to a specific browsing session.
createdstringfalseTransaction date as ISO 8601. Defaults to the time Swetrix receives the request — useful when backfilling.
metadataobjectfalseArbitrary string key/value pairs stored alongside the transaction (max 100 keys, 2000 characters total).

Endpoints

POST /log

This endpoint records pageview events.

Request
curl -i -X POST https://api.swetrix.com/log \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/116.0" \
  -H "X-Forwarded-For: 192.0.2.1" \
  -H "Content-Type: application/json" \
  -d '{"pid":"YOUR_PROJECT_ID","lc":"en-US","pg":"/"}'
Response (201 Created)
{}

POST /log/custom

This endpoint records custom events.

Request
curl -i -X POST https://api.swetrix.com/log/custom \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/116.0" \
  -H "X-Forwarded-For: 192.0.2.1" \
  -H "Content-Type: application/json" \
  -d '{"pid":"YOUR_PROJECT_ID","lc":"en-US","pg":"/"}'
Response (201 Created)
{}

POST /log/hb

This endpoint is used for heartbeat events. Heartbeat events are used to determine if the user session is still active. This allows you to see the 'Live Visitors' counter in the Dashboard panel. It's recommended to send heartbeat events every 30 seconds. We also extend the session lifetime after receiving a pageview or custom event.

Request
curl -i -X POST https://api.swetrix.com/log/hb \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/116.0" \
  -H "X-Forwarded-For: 192.0.2.1" \
  -H "Content-Type: application/json" \
  -d '{"pid":"YOUR_PROJECT_ID"}'

WebSocket Heartbeat

For more efficient heartbeat tracking (like long-running sessions, desktop applications, etc.), you can use the WebSocket interface as an alternative to the HTTP endpoint.

Connection Details:

  • URL: https://api.swetrix.com
  • Namespace: /hb
  • Query Parameters:
    • pid (Required): Your Project ID.

Events:

  • connected: Emitted by the server when the session is successfully established.
  • error: Emitted if the connection is rejected (e.g., missing PID, invalid origin, or bot detected).

Example (using socket.io-client):

import { io } from "socket.io-client";

const socket = io("https://api.swetrix.com/hb", {
  query: {
    pid: "YOUR_PROJECT_ID",
  },
  transports: ["websocket"], // Optional: force WebSocket transport
});

socket.on("connected", () => {
  console.log("Heartbeat session established");
});

socket.on("error", (err) => {
  console.error("Heartbeat error:", err.message);
});

// The server automatically handles the heartbeat interval (every 60s)
// Keep the connection open as long as the user is active on the page
Response (201 Created)
{}

POST /log/identify

This endpoint identifies the visitor with your own user ID (e.g. after they log in). It links the visitor's current anonymous profile — derived from the request's IP address and User-Agent — to the identified profile derived from the supplied profileId, so the activity recorded before identification is attributed to the identified profile in the dashboard.

After calling it, keep sending the same profileId field with your pageview / custom event payloads so subsequent events are stamped with the identified profile directly. The tracking script does both automatically via swetrix.identify().

Notes:

  • An anonymous profile is linked to the first identified profile that claims it — repeated calls with the same profileId are idempotent, and calls with a different profileId won't re-link the anonymous history (but are otherwise harmless).
  • Since anonymous profiles rotate monthly, the linking covers the visitor's recent anonymous activity — up to about a month back on that device and network.
  • Values like null, undefined, guest or an empty string are rejected with a 400 error, as they would fuse unrelated visitors into a single profile.
  • The optional traits object is stored against the identified profile and displayed on its dashboard page. Traits are merged per key, so a later call only overwrites the keys it carries and an empty / null value removes one.
Request
curl -i -X POST https://api.swetrix.com/log/identify \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/116.0" \
  -H "X-Client-IP-Address: 192.0.2.1" \
  -H "Content-Type: application/json" \
  -d '{"pid":"YOUR_PROJECT_ID","profileId":"user-12345","traits":{"email":"john@example.com","plan":"premium"}}'
Response (201 Created)
{ "profileId": "usr_user-12345" }

The returned profileId is the identified profile ID your events are stored under — the same value you'd use for revenue attribution.

POST /log/error

This endpoint records error events. These error events are later aggregated in the dashboard under the Errors tab.

Request
curl -i -X POST https://api.swetrix.com/log/error \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/116.0" \
  -H "X-Forwarded-For: 192.0.2.1" \
  -H "Content-Type: application/json" \
  -d '{"pid":"YOUR_PROJECT_ID","lc":"en-US","pg":"/","name":"ParseError","message":"Malformed input","lineno":12,"colno":510,"filename":"https://example.com/assets/convert.js"}'
Response (201 Created)
{}

POST /log/revenue

This endpoint records revenue transactions (sales, subscription charges, and refunds). Use it when you sell through a platform that isn't Stripe or Paddle (Shopify, JTL Shop, Lemon Squeezy, in-app purchases, custom checkouts, etc.). For Stripe and Paddle, prefer the built-in integrations under Project Settings > Revenue instead — they sync automatically every 30 minutes.

This endpoint requires the X-Api-Key header with your personal API key (it does not use the public pid-only auth model of the other /log/* endpoints). Never call it from the browser.

The first call against a project automatically enables API revenue tracking and locks in the currency you sent as the project's reporting currency. You can change it later from Project Settings > Revenue.

Request
curl -i -X POST https://api.swetrix.com/log/revenue \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pid":"YOUR_PROJECT_ID","transactionId":"order_42891","type":"sale","amount":49.99,"currency":"EUR","productName":"Pro plan (yearly)","profileId":"user-12345"}'
Response (201 Created)
{
  "success": true,
  "transactionId": "order_42891"
}

Returns 409 Conflict if a Stripe or Paddle key is already connected for this project — disconnect it from Project Settings first to avoid double-counting.

See the Revenue Tracking guide for full setup walkthroughs (cURL, PHP / JTL Shop, Node.js), idempotency rules, and user attribution.

Status and error codes

201 Created

The request was successful and the event was recorded.

400 Bad Request

This error is usually returned when the request body is malformed (for example, the pid parameter is missing) or the project is disabled.

402 Payment Required

This error is usually returned when the project is not active (i.e. the subscription has expired or the quota has been exceeded). Please go to the Plan and billing page to check the current status and usage of your subscription.

403 Forbidden

This error is usually returned when the unique parameter is set to true and the event is not unique, i.e. the pageview event has already been recorded for this session. For POST /log/revenue it indicates that your API key does not have permission to manage the target project.

409 Conflict

Returned by POST /log/revenue when a Stripe or Paddle key is already connected for the project. Disconnect the existing source from Project Settings > Revenue before sending revenue via the API, otherwise transactions would be double-counted.

500 Internal Server Error

This error is usually returned when the server is unable to process the request due to a temporary problem (for example, the database is unavailable). If you receive this error, please try again later. If the problem persists, please contact us.

API wrappers

NodeJS

Currently we provide an official NodeJS API wrapper for the events API. You can find how to install and use it by opening its npm package page.

Contributing

We welcome any countributions to Swetrix. If you want to create your own API wrapper, feel free to do so. Please contact us and we will list your library here.

Also as an example, feel free to check out the source code of our NodeJS API wrapper.

Common request examples

Recording a pageview event using JavaScript fetch API

fetch("https://api.swetrix.com/log", {
  method: "POST",
  headers: {
    "User-Agent": "FORWARD_CLIENT_USER_AGENT_HERE",
    "X-Forwarded-For": "FORWARD_CLIENT_IP_ADDRESS_HERE",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    pid: "YOUR_PROJECT_ID",
    lc: "en-US",
    pg: "/",
    tz: "UTC",
    unique: false,
  }),
});

Recording a custom event using Go net/http

package main

import (
  "bytes"
  "encoding/json"
  "net/http"
)

func main() {
  payload, err := json.Marshal(map[string]interface{}{
    "pid": "YOUR_PROJECT_ID",
    "ev": "YOUR_EVENT_NAME",
    "unique": false,
  })
  if err != nil {
    panic(err)
  }

  req, err := http.NewRequest("POST", "https://api.swetrix.com/log/custom", bytes.NewBuffer(payload))
  if err != nil {
    panic(err)
  }

  req.Header.Set("User-Agent", "FORWARD_CLIENT_USER_AGENT_HERE")
  req.Header.Set("X-Forwarded-For", "FORWARD_CLIENT_IP_ADDRESS_HERE")
  req.Header.Set("Content-Type", "application/json")

  client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
    panic(err)
  }
  defer resp.Body.Close()
}

Recording a heartbeat event using PHP cURL

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.swetrix.com/log/hb');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
  'pid' => 'YOUR_PROJECT_ID',
]));

$headers = [
  'User-Agent: FORWARD_CLIENT_USER_AGENT_HERE',
  'X-Forwarded-For: FORWARD_CLIENT_IP_ADDRESS_HERE',
  'Content-Type: application/json',
];

curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$result = curl_exec($ch);
curl_close($ch);

On this page