pieowl

How to Track Events with Node.js

Add server-side analytics to your Node.js app. Track events, identify users and analyze behavior with the Pieowl JavaScript SDK.

Pieowl TeamBeginner · 7 min · 8 Oct 2026
Guide · Node.js

Introduction

Server-side analytics lets you decide exactly what you track, and ad blockers can't stop it. The Pieowl JavaScript SDK runs in Node.js, so you can track events from your backend, API routes and background jobs.

Pieowl gives you product analytics, such as funnels, retention and user profiles, without cookies.

Prerequisites

Important: Server-side tracking needs a clientSecret.

Step 1: Install the SDK

Install the Pieowl JavaScript SDK:

npm install @pieowl/sdk

Or with pnpm:

pnpm add @pieowl/sdk

Step 2: Initialize Pieowl

Create a Pieowl instance with your credentials:

lib/pieowl.js
import { Pieowl } from '@pieowl/sdk';

export const pieowl = new Pieowl({
  clientId: 'YOUR_CLIENT_ID',
  clientSecret: 'YOUR_CLIENT_SECRET', // Required on the server
});

Security note: Never put your clientSecret in browser code. Use environment variables in production.

Using environment variables

lib/pieowl.js
import { Pieowl } from '@pieowl/sdk';

export const pieowl = new Pieowl({
  clientId: process.env.PIEOWL_CLIENT_ID,
  clientSecret: process.env.PIEOWL_CLIENT_SECRET,
});
.env
PIEOWL_CLIENT_ID=your-client-id
PIEOWL_CLIENT_SECRET=your-client-secret

Step 3: Track events

Track events anywhere in your Node.js app:

routes/api/signup.js
import { pieowl } from '@/lib/pieowl';

export async function POST(request) {
  const { email, name } = await request.json();

  // Your signup logic
  const user = await createUser({ email, name });

  // Track the signup
  pieowl.track('user_signed_up', {
    profileId: user.id,
    source: 'website',
  });

  return Response.json({ success: true, user });
}

Track API requests

middleware/analytics.js
import { pieowl } from '@/lib/pieowl';

export function analyticsMiddleware(req, res, next) {
  pieowl.track('api_request', {
    method: req.method,
    path: req.path,
  });

  next();
}

Track background jobs

jobs/send-email.js
import { pieowl } from '@/lib/pieowl';

export async function sendEmailJob(userId, emailData) {
  try {
    await sendEmail(emailData);

    pieowl.track('email_sent', {
      profileId: userId,
      email_type: emailData.type,
    });
  } catch (error) {
    pieowl.track('email_failed', {
      profileId: userId,
      email_type: emailData.type,
      error: error.message,
    });
  }
}

Passing profileId links the event to that user's profile.

Step 4: Identify users

Use identify to save a user's name, email and custom properties to their profile:

routes/api/login.js
import { pieowl } from '@/lib/pieowl';

export async function POST(request) {
  const { email, password } = await request.json();

  const user = await authenticateUser(email, password);

  // Save the profile
  pieowl.identify({
    profileId: user.id,
    firstName: user.firstName,
    lastName: user.lastName,
    email: user.email,
    properties: {
      plan: user.plan,
      signupDate: user.createdAt,
    },
  });

  // Track the login
  pieowl.track('user_logged_in', {
    profileId: user.id,
    method: 'email',
  });

  return Response.json({ success: true, user });
}

One instance on a server handles many users. identify also remembers the profile ID on that instance, so a later event without a profileId could be linked to the wrong person. Always pass profileId with each event on the server.

Track user actions

routes/api/purchase.js
import { pieowl } from '@/lib/pieowl';

export async function POST(request) {
  const { userId, productId, amount } = await request.json();

  pieowl.track('purchase_completed', {
    profileId: userId, // Link the event to the user
    product_id: productId,
    amount,
    currency: 'USD',
  });

  await processPurchase(userId, productId, amount);

  return Response.json({ success: true });
}

Verify your setup

  1. Call the API endpoints that track events
  2. Run a background job that tracks events
  3. Open the Pieowl dashboard
  4. Check the realtime view to see events come in

Not seeing events?

  • Check your server logs for errors
  • Make sure your Client ID and Client Secret are correct
  • Make sure you passed clientSecret (it is required on the server)

Common patterns

Track webhook events

routes/api/webhook.js
import { pieowl } from '@/lib/pieowl';

export async function POST(request) {
  const payload = await request.json();

  pieowl.track('webhook_received', {
    source: payload.source,
    event_type: payload.type,
  });

  await processWebhook(payload);

  return Response.json({ success: true });
}

Set global properties

Global properties are added to every event this instance sends.

lib/pieowl.js
import { Pieowl } from '@pieowl/sdk';

export const pieowl = new Pieowl({
  clientId: process.env.PIEOWL_CLIENT_ID,
  clientSecret: process.env.PIEOWL_CLIENT_SECRET,
});

pieowl.setGlobalProperties({
  app_version: process.env.APP_VERSION,
  environment: process.env.NODE_ENV,
  server_region: process.env.SERVER_REGION,
});

Track errors

middleware/error-handler.js
import { pieowl } from '@/lib/pieowl';

export function errorHandler(err, req, res, next) {
  pieowl.track('error_occurred', {
    error_message: err.message,
    path: req.path,
    method: req.method,
  });

  res.status(500).json({ error: 'Internal server error' });
}

Increment user properties

The profile must already exist, for example from an earlier identify call.

import { pieowl } from '@/lib/pieowl';

// Add 1 to the login count
pieowl.increment({
  profileId: user.id,
  property: 'login_count',
  value: 1,
});

Serverless and Vercel

On serverless platforms like Vercel, use waitUntil so the event is sent before the function stops:

api/track.js
import { waitUntil } from '@vercel/functions';
import { pieowl } from '@/lib/pieowl';

export default async function handler(req, res) {
  // Responds right away and keeps the function alive until the event is sent
  waitUntil(pieowl.track('important_event', { foo: 'bar' }));

  return res.status(200).json({ success: true });
}

Next steps

Why do I need a clientSecret for server-side tracking?

A browser sends an origin that Pieowl checks against your site settings. A server does not. The client secret proves the events come from you and blocks anyone else from sending events to your site.

Will tracking block my app?

No. Each call sends the event in the background and returns a promise. You only need to await it in serverless functions, so the event is sent before the function stops.

Does Pieowl help with GDPR?

Pieowl is built for GDPR: no cookies, no stored IPs. With server-side tracking, you also decide exactly which data you send. Read our cookieless analytics guide to learn more.

Your first dashboard in five minutes.

14-day free trial, no card needed. Cloud in the EU. No cookies.