Skip to content
HitKeep
Select language
Select theme
GitHub
Start free in Cloud

Server-Side Pageview and Event Tracking in HitKeep

Server-side tracking lets a backend, CMS plugin, reverse proxy, or import job send pageviews and events to HitKeep. Use it when the server already sees the original request and you need HitKeep dashboards to use the original visit time.

Use these endpoints for server-originated records. Browser pages keep using hk.js. Each request contains one pageview or event and requires an API client token.

AI crawler fetches use a separate site-scoped endpoint. Use this guide for trusted pageviews and events; use AI Fetch Ingest when you are forwarding GPTBot, ClaudeBot, PerplexityBot, or other crawler requests from edge or origin logs.

Use server-side tracking for:

  • replaying historical web server, reverse proxy, or CMS logs
  • forwarding live pageviews from an application server
  • recording server-confirmed events such as purchases, trial starts, webhook outcomes, or account upgrades
  • building first-party integrations where the backend has the original request URL, visitor IP, user agent, and event time
EndpointUse it forSite resolutionTime and visitor contextAuthentication
POST /ingestBrowser pageviews sent by hk.jsBrowser Origin headerHitKeep uses request time and derives the visitor IP from the HTTP request and trusted proxy settingsPublic tracker endpoint with CORS
POST /api/ingest/server/pageviewTrusted server-side pageviews, live forwarding, CMS plugins, reverse proxy forwarding, and historical pageview replayHostname in the submitted urlCaller sends timestamp, visitor_ip, and user_agent from the original request or log recordAPI-client token or scoped OAuth connection with current site.manage_data
POST /api/ingest/server/eventTrusted server-side custom events such as purchases, signup milestones, webhook outcomes, and account upgradesHostname in the submitted urlCaller sends timestamp; original visitor context is optional for anonymous durable eventsAPI-client token or scoped OAuth connection with current site.manage_data

The server-side pageview endpoint is beneficial because it has a contract for trusted non-browser clients. You can preserve the original analytics timestamp during log replay, pass transient visitor IP context for exclusions, spam filtering, and country, region, city, provider, and ASN lookup, and send requests from jobs or backend services that do not have browser Origin headers.

Server-side event factHitKeep behavior
Event endpointPOST /api/ingest/server/event
Pageview endpointPOST /api/ingest/server/pageview
AuthenticationAPI client bearer token with site.manage_data, or API-resource OAuth token with ingest:write, explicit site consent, and current site.manage_data
Record shapeOne pageview or event per request
Time handlingCaller-provided RFC3339 timestamp is the analytics time
Visitor IPUsed transiently for exclusions, spam filtering, and country, region, city, provider, and ASN lookup; derived metadata fields are stored
Broader factsSee Facts and Limits

Create an API client with an explicit site.manage_data grant for the site you want to write to. Team API clients are a good fit for shared integrations because they are not tied to one user’s account.

See API Clients for token creation and rotation.

Use the token as a bearer token:

Authorization: Bearer hk_live_...

Keep this token on the server. Store it in an environment variable or secrets manager.

Interactive integrations can use OAuth instead of a copied API-client token. Authorize the canonical HTTPS instance for its API resource, request ingest:write, and select the sites the integration may write to. The user must still have site.manage_data for each site.

Send the OAuth access token as a bearer token and include site_id in every pageview or event body. That UUID must be explicitly consented and match the site HitKeep resolves from the submitted url; mismatches are rejected. Existing API-client payloads do not need this additional field. Use the canonical instance for server-side ingest even when browser tracking uses a custom domain.

OAuth access tokens expire and refresh tokens rotate. Keep both server-side and implement the advertised refresh and revocation flow, or use an integration such as the WordPress plugin that manages it for you.

The timestamp field is the analytics timestamp. For historical replay, send the time from the original log entry or source system.

If your customer imports logs on May 10, 2026 for traffic that happened on April 3, 2026, send the April timestamp. HitKeep places the pageview or event in April dashboard buckets.

{
"timestamp": "2026-04-03T12:30:45Z"
}

Use RFC3339 timestamps. UTC timestamps ending in Z are the simplest option.

HitKeep reads the hostname from the submitted url, normalizes www., finds the configured site, and then checks that the API client has a site.manage_data grant for that site.

For a configured site example.com, all of these resolve to that site:

https://example.com/docs
https://www.example.com/pricing?utm_source=newsletter

The url should be the visitor-facing page URL, not the HitKeep API URL.

Pageviews require the fields below. Events require url, timestamp and name; they may omit both visitor_ip and user_agent when original visitor context is unavailable. If either visitor field is supplied, both must be valid. Do not fabricate visitor identity for delayed jobs.

FieldMeaning
urlAbsolute visitor-facing URL. HitKeep uses the hostname for site resolution and the path/query for page context.
timestampCanonical analytics time in RFC3339 format. Historical replays should use the original visit time.
visitor_ipTrusted transient visitor IP context from the original request. HitKeep uses it to derive country, region, city, provider, and ASN metadata.
user_agentUser agent from the original visitor request.

Events also require name.

For durable server events, supply an optional event_id containing up to 128 ASCII letters, digits, dots, underscores, colons or hyphens. Keep the same identifier and payload for every retry of the same logical event. HitKeep scopes that identity to the resolved site and reuses its durable ingest deduplication; omitting the identifier preserves independent-event behavior. A purchase’s transaction_id property is not a substitute for this retry key.

An optional existing opaque session_id can connect server events to browser-session attribution without retaining visitor IPs. Anonymous events have no inferred visitor IP or user agent; path, grant and Do Not Track checks still apply, but unknown visitor-IP or user-agent exclusions cannot be evaluated. See WooCommerce tracking for the first-party checkout handoff.

For pageviews, HitKeep reads utm_source, utm_medium, utm_campaign, utm_term, and utm_content from the query string in url.

HitKeep uses visitor_ip during ingest for IP exclusions, spam filtering, and country, region, city, provider, and ASN lookup. It stores the derived analytics fields and does not store the raw visitor IP.

Server-side pageview and event ingest use the ingest rate limiter, not the general API limiter. The default is 20 requests per second per IP with a burst of 40. Increase HITKEEP_INGEST_RATE_LIMIT and HITKEEP_INGEST_BURST if a log forwarder, CMS plugin, or edge worker sends many records from one IP.

In clustered deployments, any node can receive these requests. Followers forward server-side ingest to the leader before API client authentication and queueing. The leader validates the token, resolves the site, and publishes accepted records to the embedded NSQ ingest pipeline.

Send one record per request. HitKeep does not expose a batch request format for these endpoints yet.

If Caddy already serves or reverse-proxies the site you want to track, you can keep tracking at the proxy layer instead of adding hk.js to those pages. Ferdinand Mütsch maintains a community Caddy HitKeep plugin for that setup.

The plugin forwards pageviews to POST /api/ingest/server/pageview, so it follows the same contract described in this guide: keep the API token server-side, use an API client with site.manage_data, and send the visitor-facing URL to HitKeep. Its Caddy directive supports an API key, an optional configured site domain, and an optional HitKeep base URL for self-hosted or cloud instances.

Use the plugin README for the Caddyfile syntax, directive ordering, matcher examples, and local build command. The community discussion has the original announcement.

This example replays a pageview from April 3, 2026. The dashboard buckets the pageview at 2026-04-03T12:30:45Z, even if the replay job runs later.

Terminal window
curl -X POST "https://analytics.example.com/api/ingest/server/pageview" \
-H "Authorization: Bearer $HITKEEP_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.example.com/docs/start?utm_source=newsletter",
"timestamp": "2026-04-03T12:30:45Z",
"visitor_ip": "203.0.113.42",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15",
"referrer": "https://search.example/",
"language": "en-US",
"session_id": "018f5b11-43cb-7c0e-9d2d-3f0e7c68a001",
"page_id": "018f5b11-43cb-7c0e-9d2d-3f0e7c68a002"
}'

Successful ingest returns 202 Accepted with an empty response body. HitKeep queues accepted records through its embedded NSQ pipeline before DuckDB writes happen.

If a request exceeds the ingest limiter, HitKeep returns 429 Too Many Requests.

If session_id is omitted, HitKeep generates a new standalone session for that record.

If page_id is omitted for a pageview, HitKeep generates one.

Do not add utm_source, utm_medium, utm_campaign, utm_term, or utm_content as top-level JSON fields. HitKeep ignores those fields on this endpoint and reads campaign attribution from url.

Use the event endpoint when the historical source contains a named action instead of a pageview.

Terminal window
curl -X POST "https://analytics.example.com/api/ingest/server/event" \
-H "Authorization: Bearer $HITKEEP_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://www.example.com/pricing",
"timestamp": "2026-04-03T12:34:10Z",
"visitor_ip": "203.0.113.42",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15",
"name": "signup_started",
"properties": {
"plan": "pro",
"source": "pricing"
},
"session_id": "018f5b11-43cb-7c0e-9d2d-3f0e7c68a001"
}'

Events with a matching session_id can share context with pageviews in the same session. Standalone events still count in event analytics and event-based goals. Link events to pageviews with session_id when you want richer session context.

For live forwarding, use the current request time as the canonical timestamp and pass the request’s original IP and user agent.

const payload = {
url: `${req.protocol}://${req.headers.host}${req.originalUrl}`,
timestamp: new Date().toISOString(),
visitor_ip: req.ip,
user_agent: req.get('user-agent') || 'unknown',
};
await fetch('https://analytics.example.com/api/ingest/server/pageview', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HITKEEP_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});

If the app is behind a proxy, make sure req.ip is the real client IP according to your framework’s trusted proxy settings.

Send dnt: true when the source system knows the visitor asked not to be tracked:

{
"url": "https://www.example.com/private",
"timestamp": "2026-04-03T12:30:45Z",
"visitor_ip": "203.0.113.42",
"user_agent": "Mozilla/5.0",
"dnt": true
}

HitKeep accepts the request and applies the same privacy behavior as browser tracking.