npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

umami-server-tracker

v2.0.0

Published

Server-side Umami analytics client for bots, APIs, and workers. Maps users without a browser to Umami visitors, with first-class Telegram helpers.

Readme

umami-server-tracker

Server-side Umami client for environments that have users but no browser: Telegram bots, Discord bots, REST APIs, workers, payment webhooks.

Official @umami/node talks to /api/send. This package adds the missing visitor identity layer Umami needs when the request does not come from a page view:

  • a valid User-Agent (Umami rejects requests without one, and may drop bot-like agents)
  • a stable X-Forwarded-For derived from the full visitor id, so unique visitors stay unique
  • CF-IPCountry inferred from language / Telegram language_code
  • helpers that accept a Telegraf / grammY ctx, a Telegram from user, or a raw id

Zero runtime dependencies. Node.js 18+ (native fetch). ESM and CommonJS.

Install

npm install umami-server-tracker

Why not @umami/node?

@umami/node is enough for a backend that already has a real client IP and User-Agent. Bots do not. Without a synthetic visitor:

  • events are stored without a country
  • many users collapse into one visitor
  • Umami bot detection may ignore the request even when HTTP 200 is returned

This client sends the same /api/send payload Umami expects, plus visitor headers.

Quick start

import { createUmamiTracker } from 'umami-server-tracker';

const umami = createUmamiTracker({
  host: process.env.UMAMI_HOST,       // https://analytics.example.com
  websiteId: process.env.UMAMI_WEBSITE_ID,
  hostname: 'my-api',
});

await umami.track('payment_success', {
  userId: order.userId,
  language: 'en-US',
  data: { revenue: 4.99, currency: 'USD' },
});

If host or websiteId is missing, calls become no-ops. Network errors are swallowed unless throwOnError: true.

Telegram bots

import { createTelegramTracker } from 'umami-server-tracker';

const umami = createTelegramTracker({
  host: process.env.UMAMI_HOST,
  websiteId: process.env.UMAMI_WEBSITE_ID,
  hostname: 'gdz-tutor',
});

// Telegraf / grammY context
umami.track(ctx, 'START', { referral: true });

// Telegram `from` object
umami.track(ctx.from, 'HELP');

// Raw chat id
umami.track(ctx.from.id, 'SOLVE_OK', { cached: true });

createTelegramTracker() is createUmamiTracker({ identity: 'telegram' }). It uses a Telegram-like User-Agent and a documentation-range IPv6 derived from the full signed Telegram id (users, groups, and channels never share an address).

Drop-in logEvent

Matches the helpers copied across several bots (eventType / event_type, userId / user_id, optional ctx):

umami.logEvent({
  eventType: 'START',
  userId: ctx.from.id,
  eventProperties: { referral: true },
  language: ctx.from.language_code,
});

umami.logEvent({
  event_type: 'HELP',
  user_id: ctx.from.id,
  ctx,
});

API

createUmamiTracker(options) / createTelegramTracker(options)

| Option | Default | Description | | --- | --- | --- | | host | | Umami origin, trailing slash optional | | websiteId | | Website UUID | | hostname | localhost | Reported hostname | | timeout | 2500 | Request timeout, ms | | identity | generic (telegram in createTelegramTracker) | telegram | generic | none | | userAgent | built from identity | String or (userId) => string | | ipAddress | stable documentation IPv6 from userId | String or (userId) => string | | defaultCountry | | Used when language cannot be mapped | | languageCountry | built-in map (uk→UA, ru→RU, …) | Overrides merged on top of defaults | | throwOnError | false | Reject on HTTP/network errors | | onError | | (error) => void | | fetch | globalThis.fetch | Custom fetch | | enabled | auto | Force on/off | | urlFromName | (name) => '/' + name | Payload url | | maxNameLength | 50 | Umami event name limit | | maxDataStringLength | 500 | Truncate JSON-stringified property values |

Methods

tracker.track(name, input?)
tracker.track(input)
tracker.track(ctxOrUserId, name, data?)
tracker.logEvent(input)
tracker.pageview({ url, userId, language, data })
tracker.identify({ userId, data })
tracker.send(...)      // same overloads as track, returns { ok, status, error }
tracker.sendBatch(events)
tracker.enabled

track / logEvent / pageview never throw by default. Use send (or throwOnError) when you need the result.

Event data values must be string / number / boolean for Umami. Nested values are JSON.stringify'd and clipped.

Identity and geo

Umami attributes a visitor from IP + User-Agent, and country from GeoIP or CF-IPCountry.

Telegram Bot API ids are already unique across peer types, but they are large signed 64-bit values — not 1 and 2:

| Peer | Typical value | Bot API range | | --- | --- | --- | | User | 24342342342 | 1 … 1099511627775 | | Basic group | -123456789 | -999999999999 … -1 | | Supergroup / channel | -1002123456789 | -1997852516352 … -1000000000001 |

Numeric ids are written into the RFC 3849 documentation prefix 2001:db8::/32. The third hextet is the kind (0 users, 2 negative chats, 1 hashed string ids), then the 64-bit magnitude:

2001:db8:{kind}:0:{id 64-bit as 4 hextets}

User 1 becomes 2001:db8:0:0:0:0:0:1, user 24342342342 becomes 2001:db8:0:0:0:5:aaea:aac6, group -1 becomes 2001:db8:2:0:0:0:0:1. That space is large enough that Telegram ids never wrap or alias each other. Country is not taken from that IP; it comes from language:

  1. region subtag (uk-UA → UA, en-GB → GB)
  2. language map (uk → UA)
  3. defaultCountry if set

Override the map when a product is region-specific:

createTelegramTracker({
  host,
  websiteId,
  hostname: 'war-alert',
  defaultCountry: 'UA',
  languageCountry: { ru: 'UA' },
});

Self-hosted Umami

Bot traffic can still be dropped by Umami bot detection even after a 200 response. If events never appear, set DISABLE_BOT_CHECK=1 on the Umami instance, or keep the Telegram/generic User-Agent this package sends.

Cloud and self-hosted both use POST {host}/api/send. For Umami Cloud, host is https://cloud.umami.is.

TypeScript

Types are included. ctx is structurally typed — you do not need Telegraf or grammY as a dependency.

License

MIT