Sign up

Installation guides

Node.js and server SDK (@metricraven/node)

On this page

@metricraven/node sends what only your backend knows: confirmed sign-ups, verified identities, payments and refunds. It runs anywhere fetch does, including Node 18+, Bun, Deno, Cloudflare Workers and Vercel Edge, and it has no dependencies.

It wraps the same secret-key endpoints as server events, identify and the Payments API, with types, timeouts and retries. Calls never throw into your request handler: each one returns a result.

Install

  1. Add the package

    terminal
    npm install @metricraven/node
  2. Set your secret key on the server

    Copy the site secret key (ana_sec_…) from Settings and store it as METRICRAVEN_SECRET_KEY. Never ship it to the browser or a mobile app.

  3. Send an event after the action succeeds

    server.ts
    // lib/metricraven.ts — server only. Reads METRICRAVEN_SECRET_KEY.
    import { createMetricRaven, getVisitorId } from "@metricraven/node";
    
    export const metricraven = createMetricRaven();
    
    // In your sign-up route handler, after the account exists:
    await metricraven.track({
      name: "signup",
      userId: user.id,
      visitorId: getVisitorId(request), // the tracker cookie, or null
      props: { plan: "free" },
    });

Verify it works

The event appears in the Goal tab at the bottom of Traffic after refreshing. If it doesn't, log result.error and check it against the table under Results and errors below.

Calls

CallWhat it sends
track({ name, userId?, visitorId?, props?, timestamp? })A goal (server event). Needs userId or visitorId. Up to 10 props.
identify({ visitorId, userId, email?, name?, traits? })A verified link from a visitor to your user. It replaces any identity the browser claimed.
payment({ amount, currency, transactionId, visitorId?, userId?, plan?, customerId?, renewal?, isFreeTrial?, email? })A payment in major currency units. Repeats of a transactionId are ignored. Without visitorId or userId, email matches a visitor identified with it or a browser that called payment({ email }); see Attribute by email.
refund({ amount, currency, transactionId })A refund, with the cumulative amount refunded so far.
crawl({ url, userAgent, status?, ip? })One crawler request. Uses the bot token when one is set. For middleware, use @metricraven/ai-crawl.
Identify
await metricraven.identify({
  visitorId,
  userId: user.id,
  email: user.email,
  name: user.name,
  traits: { plan: "pro" },
});
Payments and refunds
// After the payment succeeds. amount is in major units: 29 means $29.
await metricraven.payment({
  amount: 29,
  currency: "USD",
  transactionId: order.id,
  visitorId,
  userId: user.id,
  plan: "pro",
});

// Refunds send the cumulative amount refunded so far.
await metricraven.refund({ amount: 15, currency: "USD", transactionId: order.id });

track, identify, payment, refund and crawl are also exported as functions that use a shared client configured from the environment, or from configure(options).

Bot token for crawler reports

If a server only reports crawlers, give it the site's ingest-only bot token (mrbot_…) as METRICRAVEN_BOT_TOKEN or botToken, instead of the secret key. It can only send crawl events.

ConfiguredWhat happens
Bot token onlycrawl() uses it. track, identify, payment and refund return bot_token_cannot_track (…_identify, …_payment, …_refund) without a request.
Bot token and secret keycrawl() uses the bot token; every other call uses the secret key.
siteId (METRICRAVEN_SITE_ID)Optional. Crawl reports are refused (wrong_site) if the token belongs to another website.

Attribute to the visit

getVisitorId(request) reads the visitor ID from a Fetch Request, a Node or Express request, Headers or a raw Cookie header. It checks the tracker's ana_vid cookie, then the metricraven_visitor_id cookie that the checkout recipes set. It returns null when there's neither.

The tracker only stores its cookie after the visitor gives storage consent. Without consent, send window.analytics.getVisitorId() from the browser with your own request, or rely on userId after identify.

Results and errors

server.ts
const result = await metricraven.track({ name: "signup", userId: user.id });
if (!result.ok) console.warn("MetricRaven:", result.error, result.status);
errorMeaning
invalid_inputRejected before sending, for example a bad event name, visitor ID or currency.
missing_secret_key / invalid_secret_keyNo key, or not an ana_sec_ key. Nothing was sent.
invalid_bot_tokenbotToken isn't an mrbot_ token. Nothing was sent.
bot_token_cannot_track (…_identify, …_payment, …_refund)Only a bot token is set, and it can only report crawlers. Nothing was sent.
unauthorizedThe key wasn't accepted, for example after a rotation's grace period ended.
invalid_payloadThe API rejected the body.
domain_not_allowedThe crawl URL's host isn't one of the site's domains.
tracking_pausedThe plan's usage limit was reached.
bot_tracking_pausedcrawl: the bot request allowance is used up until the reset.
bot_token_requiredcrawl: the website only accepts crawler reports sent with its bot token.
wrong_sitecrawl: the bot token belongs to a different website than siteId.
timeout / network_error / http_5xxNo usable response after retries.

Options

OptionWhat it does
secretKeyDefaults to METRICRAVEN_SECRET_KEY.
botTokenDefaults to METRICRAVEN_BOT_TOKEN. Used by crawl() only.
siteIdDefaults to METRICRAVEN_SITE_ID. Optional check of the bot token's website.
apiHostDefaults to METRICRAVEN_API_BASE, then https://metricraven.com.
timeoutMsPer attempt. Defaults to 5000.
retriesExtra attempts after 5xx or network errors. Defaults to 2. 4xx responses are never retried.
retryDelayMsFirst backoff wait. Defaults to 300, and doubles each time with jitter.
onErrorCalled with each failure: code, status and endpoint. Never includes the key or token.
throwOnErrorRejects instead of returning { ok: false }.
disabledSkips the network, for tests and local development.
fetchA custom fetch.

Edge and serverless

Don't hold the response while a call runs. Hand the promise to waitUntil (Workers ctx, Next.js after() or Vercel's waitUntil):

server.ts
// Cloudflare Workers: don't hold the response for analytics.
import { createMetricRaven } from "@metricraven/node";

export default {
  async fetch(request, env, ctx) {
    const metricraven = createMetricRaven({ secretKey: env.METRICRAVEN_SECRET_KEY });
    ctx.waitUntil(metricraven.track({ name: "api_call", userId: "user_42" }));
    return new Response("ok");
  },
};

Details

  • After a timeout, payments and identify are retried, because the server deduplicates or overwrites them. Events and crawls aren't retried after a timeout, because they have no deduplication key.
  • The unscoped metricraven package exposes the same API at metricraven/node.
  • ESM and CommonJS, with TypeScript types. It uses only fetch, with no Node built-ins.