Skip to main content
20% off 3 months · code LAUNCH20 · until 31 October

Report which sources pay from Node.js

A source that brings a thousand signups and no customers is worth less than one that brings twenty signups and five customers. Attribution joins where each waitlist signup came from to who is paying you in Stripe.

Keys

Secret key (nv_live_…) on your server only. There is no browser part: revenue and contact data must not reach a page a visitor can read. Create both under Dashboard → API keys.

Publishable key pasted into the page that embeds the form
NORDVA_SECRET_KEY=nv_live_…

01Server: Node.js

attribution.ts

export async function attributionReport() {
  const res = await fetch("https://api.nordva.dev/v1/attribution?group_by=referrer&period_days=90", {
    method: "GET",
    headers: {
      Authorization: `Bearer ${process.env.NORDVA_SECRET_KEY!}`,
    },
  });
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(`${error.code}: ${error.message}`);
  }
  const { data } = await res.json();
  // data.rows: [{ source, signups, confirmed, invited, paying, signup_to_paying, money: [{ currency, mrr_minor, revenue_minor }], spend?, views? }]
  // group_by: referrer (Indie) | utm_source | utm_campaign | referral_code (Builder)
  return data;
}

Signups carry their first-touch source on every plan, so the report has history from the day you upgrade. Paying and money need a connected Stripe account. Record what a channel cost with POST /v1/attribution/costs and the rows gain cost per signup, cost per paying customer and return on spend.

02Verify from a terminal

curl -s "https://api.nordva.dev/v1/attribution?group_by=referrer" \
  -H "Authorization: Bearer nv_live_…" | jq '.data.rows[] | {source, signups, paying}'

Behaviour worth knowing

  • Every response is { data, error, meta }. On failure error.code is a stable string such as VALIDATION_ERROR, PLAN_LIMIT_REACHED or RATE_LIMITED, with a remediation message.
  • POST and PATCH requests accept an Idempotency-Key header; the same key with the same body returns the original response for 24 hours.
  • Rate limits per key: 30 requests a minute on Free, 120 on Indie, 500 on Builder. A 429 carries Retry-After.
  • Browser calls with a publishable key must come from an origin registered on the project, otherwise the API answers ORIGIN_NOT_ALLOWED.