Skip to content

Server-side collection

Send events from your own server. Nothing runs in the visitor’s browser and ad blockers can’t stop it. The visitor’s IP address and user agent go to Nordtally with each event, are used in memory for the country and the daily visitor hash, and are never stored. nordtally.com measures itself this way.

1. Create an ingest key

In the dashboard, open the site, then Install → Server-side collection and create a key. It starts with ntk_ and is shown once. Keep it on the server: it lets whoever has it send events for your site.

2a. Node.js: the SDK

Node 20 or later, no dependencies. Until it is on the npm registry, install it from nordtally.com; the package name and imports stay the same when it moves.

terminal
npm install https://nordtally.com/sdk/nordtally-sdk-node-0.1.0.tgz
analytics.ts
import { createClient } from '@nordtally/sdk-node'

export const nordtally = createClient({
  siteKey: process.env.NORDTALLY_INGEST_KEY,
})

nordtally.track({
  name: 'Signup',                    // leave out for a page view
  url: 'https://shop.example/signup', // its host must be your site's domain
  referrer: req.headers.referer,
  ip: req.ip,                         // the visitor's address
  userAgent: req.headers['user-agent'],
  props: { plan: 'pro' },
})

Events are batched and sent in the background; a failed send is retried and never throws into your request. Pass gpc: true or dnt: true when the visitor’s browser sent those signals, and the site’s privacy settings apply exactly as for the script. Without ip and userAgent an event still counts, but without a visitor.

Next.js: count page views with no client code

One file. It counts real page loads only: prefetches, assets, API routes and bots are skipped.

proxy.ts
import { createClient, createNextProxy } from '@nordtally/sdk-node'

const nordtally = createClient({ siteKey: process.env.NORDTALLY_INGEST_KEY! })

export const proxy = createNextProxy({ client: nordtally })

export const config = {
  matcher: [
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      missing: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },
  ],
}

Behind a load balancer, the helper reads the visitor’s address from the right end of X-Forwarded-For, one hop by default (trustedHops). If your proxy sets its own header, pass clientIp: (req) => … instead. Client-side navigations in the App Router don’t reach the server, so this counts full page loads.

2b. Any language: the HTTP API

terminal
curl -X POST https://in.nordtally.com/api/v1/events \
  -H "Authorization: Bearer $NORDTALLY_INGEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [{
      "name": "pageview",
      "url": "https://shop.example/pricing",
      "referrer": "https://www.google.com/",
      "client_ip": "203.0.113.42",
      "user_agent": "Mozilla/5.0 ..."
    }]
  }'
FieldNotes
events1 to 100 events per request.
name, urlRequired. pageview or your event name; an absolute URL on your site’s domain.
referrer, props, revenueOptional. revenue is { "amount": "49.50", "currency": "DKK" }.
client_ip, user_agentOptional, used in memory only. Without them the event has no visitor.
gpc, dntOptional booleans: the visitor’s opt-out signals.

A 202 response lists how many events were accepted and which were dropped, by index and reason (for example a bot). Errors are 401 for a bad key, 400 for a malformed body and 429 with Retry-After when you send too fast (10,000 events per 10 seconds per key).