---
title: "Server-Side Pageview and Event Tracking in HitKeep | HitKeep"
description: "Send trusted server-side pageviews and events to HitKeep with RFC3339 timestamps, transient IP context, derived metadata, and bearer-token auth."
canonical: "https://hitkeep.com/guides/tracking/server-side-tracking/"
---

# 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.

Use the authenticated endpoint for server-side pageviews

Use `POST /api/ingest/server/pageview` for backend, CMS, proxy, edge worker, or log replay pageviews. `POST /ingest` is the compact browser tracker endpoint used by `hk.js`.

AI crawler fetches use a separate site-scoped endpoint. Use this guide for trusted pageviews and events; use [AI Fetch Ingest](https://hitkeep.com/guides/tracking/ai-fetch-ingest/) when you are forwarding GPTBot, ClaudeBot, PerplexityBot, or other crawler requests from edge or origin logs.

## When to use it

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

## Choose the ingest endpoint

| Endpoint | Use it for | Site resolution | Time and visitor context | Authentication |
| --- | --- | --- | --- | --- |
| POST /ingest | Browser pageviews sent by hk.js | Browser Origin header | HitKeep uses request time and derives the visitor IP from the HTTP request and trusted proxy settings | Public tracker endpoint with CORS |
| POST /api/ingest/server/pageview | Trusted server-side pageviews, live forwarding, CMS plugins, reverse proxy forwarding, and historical pageview replay | Hostname in the submitted url | Caller sends timestamp, visitor_ip, and user_agent from the original request or log record | API client token with site.manage_data |
| POST /api/ingest/server/event | Trusted server-side custom events such as purchases, signup milestones, webhook outcomes, and account upgrades | Hostname in the submitted url | Caller sends timestamp, visitor_ip, and user_agent from the original request or source system | API client token with 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 fact | HitKeep behavior |
| --- | --- |
| Event endpoint | POST /api/ingest/server/event |
| Pageview endpoint | POST /api/ingest/server/pageview |
| Authentication | API client bearer token with site.manage_data |
| Record shape | One pageview or event per request |
| Time handling | Caller-provided RFC3339 timestamp is the analytics time |
| Visitor IP | Used transiently for exclusions, spam filtering, and country, region, city, provider, and ASN lookup; derived metadata fields are stored |
| Broader facts | See Facts and Limits |

Do not send server payloads to `/ingest`

`/ingest` accepts the compact payload shape produced by `hk.js`, including fields such as `u_src`, `u_med`, `session_id`, and `page_id`. It also expects browser request context. Server integrations should use `/api/ingest/server/pageview` so HitKeep can validate an API client token and use the caller-provided `timestamp`, `visitor_ip`, and `user_agent`.

## Create a token

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](https://hitkeep.com/guides/security/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.

## Historical timestamps

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.

## How HitKeep chooses the site

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.

## Required fields

Pageviews and events both require:

| Field | Meaning |
| --- | --- |
| url | Absolute visitor-facing URL. HitKeep uses the hostname for site resolution and the path/query for page context. |
| timestamp | Canonical analytics time in RFC3339 format. Historical replays should use the original visit time. |
| visitor_ip | Trusted transient visitor IP context from the original request. HitKeep uses it to derive country, region, city, provider, and ASN metadata. |
| user_agent | User agent from the original visitor request. |

Events also require `name`.

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

Put UTM values in `url`

Server-side pageview payloads do not have top-level UTM fields. Send the visitor-facing URL with its campaign query string, for example `https://www.example.com/docs?utm_source=newsletter&utm_medium=email&utm_campaign=spring`. HitKeep stores the path with the query string and extracts UTM values from that 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.

## Rate limiting and clusters

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.

## Caddy reverse proxy option

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](https://github.com/muety/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](https://github.com/PascaleBeier/hitkeep/discussions/179) has the original announcement.

## Replay historical pageviews

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.

```
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`.

## Replay historical events

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

```
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.

## Live server-side forwarding

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.

## Do Not Track

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.

## API reference

OpenAPI source

The public OpenAPI schema includes `POST /api/ingest/server/pageview` and `POST /api/ingest/server/event`. A self-hosted or cloud instance serves its own runtime schema at `/api/docs/v1/openapi.json`; if that JSON only lists `/ingest` and `/ingest/event`, the instance is serving an older runtime schema than this guide.

- [Ingest server-side pageview](https://hitkeep.com/api/operations/apiingestserverpageview/)
- [Ingest server-side event](https://hitkeep.com/api/operations/apiingestserverevent/)
- [API Clients](https://hitkeep.com/guides/security/api-clients/)
- [Facts and Limits](https://hitkeep.com/reference/facts-and-limits/)
- [Community Caddy HitKeep plugin](https://github.com/muety/caddy-hitkeep-plugin)

## Related

- [Custom Events](https://hitkeep.com/guides/tracking/custom-events/)
- [Signed Outbound Webhooks](https://hitkeep.com/guides/integrations/webhooks/) when HitKeep needs to notify another system instead
- [AI Fetch Ingest](https://hitkeep.com/guides/tracking/ai-fetch-ingest/)
- [AI Fetch on AWS](https://hitkeep.com/guides/tracking/ai-fetch-aws/)
- [AI Visibility Analytics](https://hitkeep.com/guides/analytics/ai-visibility/)
- [IP Exclusions](https://hitkeep.com/guides/tracking/ip-exclusions/)
- [Bot and Spam Filtering](https://hitkeep.com/guides/tracking/spam-filtering/)
- [Tracker Architecture](https://hitkeep.com/guides/tracking/tracker-architecture/)
- [Import Plausible](https://hitkeep.com/guides/data/import-plausible/)
- [Import Simple Analytics](https://hitkeep.com/guides/data/import-simple-analytics/)

[Previous Custom events](https://hitkeep.com/guides/tracking/custom-events/)[Next UTM parameters](https://hitkeep.com/guides/tracking/utm-parameters/)
