{"_id":"@analytiics/sdk-node","_rev":"2-6094d67cf4f40330e23c064b9c236ba6","name":"@analytiics/sdk-node","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@analytiics/sdk-node","version":"0.1.0","keywords":["analytics","analytiics","revenue-attribution","server","sdk"],"author":{"name":"Charlie Clark"},"license":"MIT","_id":"@analytiics/sdk-node@0.1.0","maintainers":[{"name":"clarkcharlie03","email":"clarkcharlie03@gmail.com"}],"homepage":"https://github.com/charlieclark/analytiics/tree/main/packages/sdk-node#readme","bugs":{"url":"https://github.com/charlieclark/analytiics/issues"},"dist":{"shasum":"5f494be33013a9846f01b790e844d0d2162571d4","tarball":"https://registry.npmjs.org/@analytiics/sdk-node/-/sdk-node-0.1.0.tgz","fileCount":5,"integrity":"sha512-/EaPCoNJt5/IPgnNQVofldJK2BBTCWNCDpVOjhECZyOHBeZnoF8EP3mM6qlRBb2oN0kKc0WWozvfcS9IjMg7Aw==","signatures":[{"sig":"MEUCIQCUh5nxn8d4/kPYYnGGByPJEv9s7hqni3t4R0RNHX4YVgIgBZWlcSkte1HAGA2jE6gKWr08RcW2xc1c6Fz1LBNl7DM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":14691},"main":"./dist/index.js","type":"module","_from":"file:analytiics-sdk-node-0.1.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm --dts --clean","typecheck":"tsc --noEmit"},"_npmUser":{"name":"clarkcharlie03","email":"clarkcharlie03@gmail.com"},"_resolved":"/tmp/0d192f6bc2163c5d0811dfeaa4c005a9/analytiics-sdk-node-0.1.0.tgz","_integrity":"sha512-/EaPCoNJt5/IPgnNQVofldJK2BBTCWNCDpVOjhECZyOHBeZnoF8EP3mM6qlRBb2oN0kKc0WWozvfcS9IjMg7Aw==","repository":{"url":"git+https://github.com/charlieclark/analytiics.git","type":"git","directory":"packages/sdk-node"},"_npmVersion":"10.9.8","description":"Analytiics server SDK: revenue attribution and identity from your backend.","directories":{},"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.4.0","vitest":"^3.1.0","typescript":"^5.8.0","@types/node":"^22.13.0"},"_npmOperationalInternal":{"tmp":"tmp/sdk-node_0.1.0_1787510915687_0.409568718022713","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@analytiics/sdk-node","version":"0.2.0","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"devDependencies":{"@types/node":"^22.13.0","tsup":"^8.4.0","typescript":"^5.8.0","vitest":"^3.1.0"},"description":"Analytiics server SDK: revenue attribution and identity from your backend.","keywords":["analytics","analytiics","revenue-attribution","server","sdk"],"license":"MIT","author":{"name":"Charlie Clark"},"homepage":"https://github.com/charlieclark/analytiics/tree/main/packages/sdk-node#readme","repository":{"type":"git","url":"git+https://github.com/charlieclark/analytiics.git","directory":"packages/sdk-node"},"bugs":{"url":"https://github.com/charlieclark/analytiics/issues"},"publishConfig":{"access":"public"},"scripts":{"build":"tsup src/index.ts --format esm --dts --clean","typecheck":"tsc --noEmit","test":"vitest run"},"_id":"@analytiics/sdk-node@0.2.0","_integrity":"sha512-NZj46wrirKkJgSgtq2/AdeenwPjfuWbctRL4M8x8QkajL/oOY+Hu7U08pcIBO8OBWT5tqvR+TNOtXWFV7a+34A==","_resolved":"/tmp/43bcd4b175941457039cc75fabd116c6/analytiics-sdk-node-0.2.0.tgz","_from":"file:analytiics-sdk-node-0.2.0.tgz","_nodeVersion":"22.23.2","_npmVersion":"10.9.8","dist":{"integrity":"sha512-NZj46wrirKkJgSgtq2/AdeenwPjfuWbctRL4M8x8QkajL/oOY+Hu7U08pcIBO8OBWT5tqvR+TNOtXWFV7a+34A==","shasum":"3a308752994446f9eff0dca6881eb8a9bd6356e0","tarball":"https://registry.npmjs.org/@analytiics/sdk-node/-/sdk-node-0.2.0.tgz","fileCount":5,"unpackedSize":18067,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFC46kPQnaCZ0vr0yF5aDVNCj4VCXRIE+enVwVqhRkbpAiAviBGd9nkxcdsBr+5IIkMVhMUoA0gaRZ5b7/Dfg6buoQ=="}]},"_npmUser":{"name":"clarkcharlie03","email":"clarkcharlie03@gmail.com"},"directories":{},"maintainers":[{"name":"clarkcharlie03","email":"clarkcharlie03@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk-node_0.2.0_1788639263481_0.5930508071363085"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-23T18:48:35.542Z","modified":"2026-09-05T20:14:23.804Z","0.1.0":"2026-08-23T18:48:35.845Z","0.2.0":"2026-09-05T20:14:23.616Z"},"bugs":{"url":"https://github.com/charlieclark/analytiics/issues"},"author":{"name":"Charlie Clark"},"license":"MIT","homepage":"https://github.com/charlieclark/analytiics/tree/main/packages/sdk-node#readme","keywords":["analytics","analytiics","revenue-attribution","server","sdk"],"repository":{"type":"git","url":"git+https://github.com/charlieclark/analytiics.git","directory":"packages/sdk-node"},"description":"Analytiics server SDK: revenue attribution and identity from your backend.","maintainers":[{"name":"clarkcharlie03","email":"clarkcharlie03@gmail.com"}],"readme":"# @analytiics/sdk-node\n\nAuthenticated server events: revenue, identity links, and backend activity.\n\nThe browser tracker posts to a public, unauthenticated endpoint — it has to,\nsince the snippet ships to every visitor. That endpoint therefore **ignores\nrevenue**: if it didn't, anyone could fill a customer's dashboard with fake\nsales. Revenue arrives here instead, over an authenticated path.\n\n## Setup\n\nEach project gets a server write key. It is shown once, when it is issued, and\ncannot be recovered afterwards — only its hash is stored.\n\n```sh\n# on the app that sends server events\nANALYTIICS_SITE=liinks.co\nANALYTIICS_WRITE_KEY=sk_live_...\n```\n\nKeys are bound to a project, so a leaked key can only write to the project it\nwas issued for, and a key can be revoked without redeploying anything. Keep it\nserver-side — it is not a publishable key.\n\n```ts\nimport { createClient } from \"@analytiics/sdk-node\";\n\nexport const analytiics = createClient({\n  site: \"liinks\",\n  writeKey: process.env.ANALYTIICS_WRITE_KEY!,\n  api: \"https://in.analytiics.co\",\n});\n```\n\n## Attributing revenue to a channel\n\nA Stripe webhook knows a customer id and an amount. It has no cookie, no\nreferrer, and no session — nothing that says the customer arrived from Google\nthree days ago. The link is the **user id**, in two steps:\n\n**1. In the browser, once the user is known:**\n\n```ts\nwindow.analytiics.identify(user.id);\n```\n\nThat ties the anonymous visitor to a user id, which is what lets a later\nserver-side event find the session — and therefore the source — that earned it.\nCall it at signup and on login.\n\n**2. In the webhook:**\n\n```ts\nexport async function POST(req: Request) {\n  const event = stripe.webhooks.constructEvent(/* … */);\n\n  const delivery = createClient({\n    site: \"liinks.co\",\n    writeKey: process.env.ANALYTIICS_WRITE_KEY!,\n  });\n\n  if (event.type === \"checkout.session.completed\") {\n    const session = event.data.object;\n    delivery.revenue({\n      name: \"subscription_started\",\n      eventId: event.id, // same provider ID on every webhook replay\n      ts: new Date(event.created * 1000).toISOString(),\n      userId: session.client_reference_id!, // the same id you passed to identify()\n      revenueCents: session.amount_total!,\n      currency: session.currency!.toUpperCase(),\n      props: { plan: \"pro\" },\n    });\n  }\n\n  // Serverless runtimes can freeze the process the moment this resolves.\n  try {\n    await delivery.flush({ throwOnError: true });\n  } catch {\n    return new Response(\"Retry this webhook\", { status: 503 });\n  }\n  return new Response(null, { status: 200 });\n}\n```\n\n`revenue_by_source` then resolves `userId` → the session that user was first\nseen in → that session's entry source, and credits the revenue there. Revenue\nfrom a user who was never seen in a browser session is reported as\n`Unattributed` rather than dropped. Revenue with no user ID is also included\nthere, without inventing a customer identity.\n\nCredit goes to the **first** session, not the last: the channel that found the\ncustomer is the one worth spending on.\n\n### What this can't do\n\nVisitor ids rotate daily (they're a hash of IP + user agent, not a cookie), so\na user who first visits on Monday and signs up on Wednesday can only be linked\nback to Wednesday's session. Attribution uses the first observed identified session. A visit on a previous\nday that never carried an identity cannot reliably be linked to that user.\nKnown-user funnel steps use the user ID across days and devices.\n\n## With the generated helpers\n\nIf the project has an `analytics.yaml`, prefer the typed helpers over raw event\nnames — that's the point of the manifest:\n\n```ts\nimport { createClient, toAnalytiicsClient } from \"@analytiics/sdk-node\";\nimport { createTrack } from \"./analytics.gen.js\";\n\nconst analytiics = createClient({ site: \"thiings\", writeKey: process.env.ANALYTIICS_WRITE_KEY! });\n// Inside the request handler after resolving its user:\nconst track = createTrack(toAnalytiicsClient(analytiics, { userId: session.userId }));\n\n// `revenue: true` in the manifest makes the amount part of the signature —\n// recording the conversion without the money won't compile.\ntrack.purchase(\n  { type: \"personal_indie\" },\n  { userId, revenueCents: 4900, currency: \"USD\" },\n);\nawait analytiics.flush();\n```\n\nThe generated `track()` signature has nowhere to put a user id; it's shaped for\nthe browser, where the tracker already knows who it is. On a server there's no\nambient user, so `toAnalytiicsClient` takes one and attaches it to every\nnon-revenue event. Regenerate with `analytiics codegen` to get `createTrack`.\nNever call the shared browser `configure()` with request-specific identity.\n\n## API\n\n```ts\nclient.track({ name, eventId?, userId?, props?, url?, ts? });\nclient.identify(userId);\nclient.revenue({ name, eventId?, userId?, revenueCents, currency, props?, ts? });\nawait client.flush();   // send now — required before a serverless handler returns\nawait client.close();   // flush and stop the timer\nawait client.flush({ throwOnError: true }); // require confirmed delivery\n```\n\nThe default flush reports failures through `onError` and resolves, preserving\nbest-effort analytics for application traffic. Strict flush rejects if any event\non this client failed delivery, including an earlier automatic flush. That\nfailure stays on the client: a later successful batch cannot repair a lost event.\nUse a separate client per webhook handler when requiring strict delivery.\n\nBatches retry twice on network errors, HTTP 429 and 5xx. Each attempt has a\n25-second timeout (`requestTimeoutMs` configures it). Other 4xx errors are\nreported immediately. Each queued event gets a UUID and timestamp once, before\nany retry; properties are copied at enqueue time. Batches never exceed 200 events.\n\nFor provider webhooks, supply `eventId: event.id` and the original `ts` so a new\nhandler invocation reuses the logical event's identity. If one provider event\nproduces multiple analytics events, suffix its ID with each event's purpose.\nThe collector returns 409 for conflicting reuse. Server event IDs are required\non the wire; this SDK supplies them automatically. Older SDKs must be upgraded\nbefore switching to the new collector contract. `$` event names are reserved\nexcept `$identify`.\n\nThe production collector persists a receipt and waits for Tinybird's database\ncommit before reporting acceptance. Retries reuse the first payload/timestamp;\nqueries and aggregate states deduplicate IDs after uncertain writes. Without\nan acknowledged response, retry using the same IDs. This is not an autonomous\noutbox: the provider or caller must retry a failed delivery.\n\n`revenueCents` may be negative — refunds and chargebacks are real revenue\nevents. A `currency` is required whenever an amount is set: an amount with no\nunit can't be summed, and defaulting to USD would quietly misreport every\nnon-USD customer.\n\n### Serverless\n\nFor best-effort application events a module-scoped client is fine. For payment\nwebhooks use the request-local client and strict flush shown above. Always\nawait the flush before returning. The default batching (20 events or 5 seconds) exists for\nlong-running servers; in a function that freezes between invocations, an\nunflushed batch is a lost sale.\n","readmeFilename":"README.md"}