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

@firepixel/node

v0.3.0

Published

Server-side conversion tracking for FirePixel. One dependency-free ESM helper that normalises, hashes, signs, sends and retries your events.

Downloads

325

Readme

@firepixel/node

Server-side conversion tracking for FirePixel. You hand it the order data you already have, in the shape your app already uses. Normalising, per-network hashing, reading the attribution cookies, HMAC signing, sending and retrying all happen inside the package.

Zero runtime dependencies. One module, the same file the FirePixel headless guide walks through, comments and all.

The problem it exists to solve. Every identifier an ad network receives is a SHA-256 digest, and a digest computed under the wrong normalisation rule is still a valid-looking 64-character string. Get it wrong and nothing fails: no error, no log line, no rejected request. The conversions deliver and match nobody, and the only symptom is a match rate somebody notices three months later. This package owns those rules so your checkout does not have to.

Install

npm install @firepixel/node

Node 20.19+ or 22.12+. The package is ESM only, with no CommonJS build and therefore no dual-package hazard. require() of an ES module works on that floor, and the test suite asserts it, so a CommonJS codebase needs no changes:

const { createFirePixel } = require('@firepixel/node');

One caveat, and only for types: a TypeScript project that emits CommonJS needs "moduleResolution": "nodenext". The older "node16" predates require() of an ES module and reports TS1479 even though the call succeeds at runtime. bundler and every ESM configuration are unaffected.

Quick start

import { createFirePixel } from '@firepixel/node';

const fp = createFirePixel({ kid: process.env.FIREPIXEL_KID, secret: process.env.FIREPIXEL_SECRET });

await fp.purchase({
  eventId:  order.id,                               // your order id, so a retry deduplicates
  customer: { email, phone, firstName, lastName, country: 'us' },
  order:    { currency: 'USD', value: order.total, orderId: order.id, items },
  request:  req,                                    // the INCOMING request: IP, UA, click cookies
});

FIREPIXEL_SECRET is server side only. Never prefix it NEXT_PUBLIC_, never import it into client code, never return it from a route. Anyone holding it can sign events for your store. Both values come from Stores, then your store, then Credentials in the FirePixel app.

Pass plaintext to customer. The package produces the digests each network actually wants (em and em_google for the two different email rules, ph and ph_e164 for the two different phone rules) plus the plaintext address block Google's addressInfo needs. Nothing unhashed leaves your network.

Events

Twelve canonical events, each with a named convenience method. fp.purchase(data) is exactly fp.track('Purchase', data).

| Method | Event | |---|---| | fp.pageView() | PageView | | fp.viewContent() | ViewContent | | fp.viewCart() | ViewCart | | fp.addToCart() | AddToCart | | fp.removeFromCart() | RemoveFromCart | | fp.addToWishlist() | AddToWishlist | | fp.search() | Search | | fp.initiateCheckout() | InitiateCheckout | | fp.addShippingInfo() | AddShippingInfo | | fp.addPaymentInfo() | AddPaymentInfo | | fp.purchase() | Purchase | | fp.refund() | Refund |

fp.track(name, data) also accepts common aliases, so GA4 spellings (view_item, begin_checkout) and Shopify's (checkout_completed) map to the canonical name instead of being dropped. Pass an array as data to send a batch.

The rest of the surface

| | | |---|---| | fp.verify(name, data) | Run the whole path and get back what WOULD be delivered, sending nothing. Do not ship with it on. | | fp.captureAttribution(request, response) | Persist ad-click ids into first-party cookies at landing. Omit response to get the Set-Cookie values back and set them yourself. | | fp.readAttribution(request) | The shopper's click ids and client hints as a plain object. Save it on the order at checkout and pass it back as identity from the payment webhook, which has no cookies of its own. | | fp.buildEvent(name, data) | The exact object that would be POSTed, without posting it. | | fp.send(events, options) | POST an already-built event. | | fp.sign(rawBody, ts) | The Authorization: FirePixel-HMAC ... header for a raw body. |

It warns, it does not throw

A tracking call must never take down a checkout. Anything wrongly typed gets a console.warn prefixed [FirePixel], and is then coerced if the intent is obvious ("19.99" is a number) or dropped if it is not ("$19.99" is not). Transport failures come back as { ok: false, error }, never as a rejected promise. The only throw is a missing kid or secret at construction, because nothing can work without them and failing at startup beats failing per event.

Logging

Pass logger to send the package's diagnostics somewhere other than console:

const fp = createFirePixel({ kid, secret, logger: myLogger });

warn is required and receives every warning as one string, prefixed [FirePixel]. debug, info and error are optional; when your logger has them, a retry is reported at debug and a send that has used up its retries at error. They are called as level(message, meta), where message is a complete sentence and meta a plain object with the same facts (attempt, delay_ms, status, attempts). That shape suits console and winston as they are; for pino, which wants the object first, wrap it: { warn: (m) => log.warn(m), debug: (m, meta) => log.debug(meta, m), error: (m, meta) => log.error(meta, m) }. Without a logger, warnings go to console.warn and the other levels are silent. The debug and error lines never include your secret, a signature or any part of an event.

The client header

Every ingest request carries X-FirePixel-Client: node/<version> (the exported SDK_VERSION), so FirePixel can see which SDK versions are still sending. It is not part of the signature and carries no credential.

Full guide

The complete walkthrough, including capturing attribution at landing, sending events from a server-rendered page, snapshotting attribution onto the order so the payment webhook can still attribute a sale, and what to do when nothing arrives, lives in the FirePixel headless integration guide and its companion example storefront.

The shipped dist/firepixel.mjs is itself the reference: every normalisation rule is commented with why it exists rather than what it does. Reading it is a supported way to use this package.

TypeScript declarations ship with the package. There is nothing to install from @types.

Developing this package

src/firepixel.ts is the only file to edit. dist/firepixel.mjs and dist/firepixel.d.mts are generated from it and are not committed.

npm install
npm run build     # generate dist/
npm test          # drift guard, then 126 tests against dist/

The runtime file is produced by module.stripTypeScriptTypes() rather than by tsc, because tsc re-prints from the AST and would destroy the blank lines and indentation on a file that is meant to be read. tsc still emits the declarations. npm run check regenerates both into a temp directory and fails if dist/ differs, so the suite cannot pass against a stale build.

Licence

MIT. See LICENSE.