Documentation / Server-Side Tracking

Server-Side Tracking

Send pageviews and custom events directly from your backend without a browser script. Useful for server-rendered pages, API-only backends, and environments where JavaScript cannot run.

Business plan required

Server-side tracking is available on the Business plan and above. Your API token must be created with the collect:write ability.

Privacy-first by design

The privacy guarantees mirror the JavaScript beacon pipeline. The ip field is hashed with a daily-rotating salt into an anonymous visitor fingerprint and used for geo lookup within the request, then discarded. The raw IP is never written to the database. The user_agent string is parsed for browser and device detection within the request, then discarded. The raw string is never stored. URLs are stripped of query strings and fragments before storage. Visitor identity is a daily-rotating SHA-256 hash, making re-identification across days cryptographically infeasible.

Unlike the browser script, the server-side API cannot automatically detect Global Privacy Control or Do Not Track signals from the end user. If your visitors use GPC or DNT, it is your responsibility to check those signals in your own request handling and omit the collect call accordingly.

Authentication

All collect endpoints require a Sanctum API token passed as a Bearer header. The token must be created with the collect:write ability selected.

HTTP header
Authorization: Bearer <YOUR_API_TOKEN>

Generate a token with the correct ability from Settings / API tokens.

Record a pageview

Send this request each time a page is served to a visitor.

POST https://ghostlyx.com/api/v1/collect/pageview

Request body (JSON)

tracking_id string Your site tracking ID (format: gx_XXXXXXXXXXXX). Found in your site settings. required
url string Full URL of the page being viewed. Query strings and fragments are stripped server-side. required
pathname string Path component of the URL, e.g. /blog/my-post. required
referrer string Referrer URL, if known. Query strings and fragments are stripped. optional
ip string Visitor IP. Hashed with a daily salt into an anonymous visitor fingerprint and used for geo lookup, then discarded. The raw IP is never written to the database. Defaults to the token-holder request IP if omitted. optional
user_agent string Visitor User-Agent. Parsed for browser and device detection, then discarded. The raw string is never stored. optional
utm_source string UTM source parameter. optional
utm_medium string UTM medium parameter. optional
utm_campaign string UTM campaign parameter. optional
utm_content string UTM content parameter. optional
utm_term string UTM term parameter. optional
timestamp integer Unix timestamp (seconds) for the pageview. Defaults to current server time if omitted. optional

Example (curl)

curl -X POST https://ghostlyx.com/api/v1/collect/pageview \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tracking_id": "gx_abc123",
    "url":         "https://example.com/blog/my-post",
    "pathname":    "/blog/my-post",
    "referrer":    "https://google.com",
    "ip":          "203.0.113.42",
    "user_agent":  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...",
    "utm_source":  "newsletter"
  }'

Example (PHP / Laravel)

use Illuminate\Support\Facades\Http;

Http::withToken(config('services.ghostlyx.token'))
    ->post('https://ghostlyx.com/api/v1/collect/pageview', [
        'tracking_id' => config('services.ghostlyx.tracking_id'),
        'url'         => $request->url(),
        'pathname'    => $request->path(),
        'referrer'    => $request->header('Referer'),
        'ip'          => $request->ip(),
        'user_agent'  => $request->userAgent(),
    ]);

Response

200 {"ok": true} Pageview recorded successfully.
200 {"ok": true, "skipped": "bot"} Request was identified as bot traffic and silently dropped.
402 {"ok": false, "reason": "cap_exceeded"} Monthly pageview cap reached.
403 Token missing the collect:write ability, or account is not on the Business plan.
404 {"ok": false} Tracking ID not found.
422 Validation failed. Check required fields.

Record a custom event

Send this request when a named business event occurs in your backend, such as a signup, a purchase, or a subscription change.

POST https://ghostlyx.com/api/v1/collect/event

Request body (JSON)

tracking_id string Your site tracking ID (format: gx_XXXXXXXXXXXX). required
name string Event name, e.g. "Signup" or "Purchase". Max 255 characters. required
url string Full URL of the page the event occurred on. Accepted for forward compatibility. Only the pathname is stored. required
pathname string Path component of the URL. required
props object Flat key-value metadata. Values must be strings, numbers, or booleans. Max 4 KB encoded. Do not include personal data. optional
ip string Visitor IP. Hashed with a daily salt into an anonymous visitor fingerprint and used for geo lookup, then discarded. The raw IP is never written to the database. optional
user_agent string Visitor User-Agent. Parsed for browser and device detection, then discarded. The raw string is never stored. optional
timestamp integer Unix timestamp (seconds) for when the event occurred. Defaults to current server time. optional

Example (curl)

curl -X POST https://ghostlyx.com/api/v1/collect/event \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tracking_id": "gx_abc123",
    "name":        "Signup",
    "url":         "https://example.com/register",
    "pathname":    "/register",
    "ip":          "203.0.113.42",
    "user_agent":  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...",
    "props": {
      "plan":     "growth",
      "referral": "product-hunt"
    }
  }'

Example (PHP / Laravel)

use Illuminate\Support\Facades\Http;

Http::withToken(config('services.ghostlyx.token'))
    ->post('https://ghostlyx.com/api/v1/collect/event', [
        'tracking_id' => config('services.ghostlyx.tracking_id'),
        'name'        => 'Signup',
        'url'         => $request->url(),
        'pathname'    => $request->path(),
        'ip'          => $request->ip(),
        'user_agent'  => $request->userAgent(),
        'props'       => [
            'plan' => $user->plan,
        ],
    ]);

Response

200 {"ok": true} Event recorded successfully.
200 {"ok": true, "skipped": "bot"} Request was identified as bot traffic and silently dropped.
402 {"ok": false, "reason": "cap_exceeded"} Monthly pageview cap reached.
403 Token missing the collect:write ability, or account is not on the Business plan.
404 {"ok": false} Tracking ID not found.
422 Validation failed. Check required fields.

Best practices

Always pass the real visitor IP

Pass the IP from the original HTTP request, not your server's outbound IP. In Laravel you can read it via $request->ip(). If you sit behind a proxy, ensure TRUSTED_PROXIES is configured correctly so Laravel resolves the visitor IP rather than the proxy IP.

Pass the visitor User-Agent

The user_agent field enables accurate browser, OS, and device breakdowns in your dashboard. Without it, all server-side hits will show as "Other" in the device report.

Fire events close to the action

Record events synchronously at the point in your code where the action succeeds. Avoid batching or delaying, as this affects real-time data in your dashboard.

Never include personal data in event names or props

Do not pass email addresses, names, database IDs, or any identifier that can be linked to a real person. This keeps your implementation GDPR and CCPA compliant without extra work.

Use Fire-and-forget for pageviews

Pageview collection should not block your response. Wrap HTTP calls in a queued job or use async HTTP (Http::async()) to avoid adding latency to page loads.

Store credentials in environment variables

Keep your API token and tracking ID out of source code. Use GHOSTLYX_TOKEN and GHOSTLYX_TRACKING_ID in your .env file and read them via config().

Ready to start sending server-side events?