billdogeng-node
v1.1.1-beta.5
Published
Official BilldogEng server SDK for Node.js — Analytics, Feature Flags (remote + local eval), Surveys, Messaging, LLM observability, and BillDog Auth token verification.
Maintainers
Readme
billdogeng-node
Official BilldogEng server SDK for Node.js / TypeScript — the engagement suite for server-side use: Analytics, Feature Flags (remote + local evaluation), Surveys (data API), Messaging dispatch, LLM observability, and BillDog Auth session-token verification.
This is the canonical reference implementation that the other
billdogeng-{python,go,php,ruby,java,rust,dotnet,elixir} SDKs mirror.
Install
npm install billdogeng-nodeRequires Node.js ≥ 18 (uses the built-in global fetch).
Quickstart
import { BilldogEng } from 'billdogeng-node';
const bd = new BilldogEng('bd_test_xxx', {
// host: 'https://api.billdog.io/v1', // default
flushAt: 20, // batch size that triggers a flush
flushInterval: 10_000, // ms background flush cadence
localEvaluation: true, // server-only: evaluate flags locally
});
// ─ Analytics ─ (batched + flushed automatically)
bd.capture('user-123', 'order_completed', { revenue: 49.99 }, { company: 'acme' });
bd.identify('user-123', { email: '[email protected]', plan: 'pro' });
bd.groupIdentify('company', 'acme', { seats: 50 });
bd.alias('user-123', 'anon-abc');
// ─ Feature flags ─
const on = await bd.isFeatureEnabled('new_checkout', 'user-123');
const variant = await bd.getFeatureFlag('paywall_test', 'user-123'); // boolean | variant key | null
const payload = await bd.getFeatureFlagPayload('paywall_test', 'user-123'); // variant config
const all = await bd.getAllFlags('user-123');
// ─ Surveys (data API) ─
const surveys = await bd.surveys.list('user-123');
const config = await bd.surveys.fetch(surveys[0].id, 'user-123');
const { respondent_id } = await bd.surveys.start(config.id, { customerId: 'user-123', idempotencyKey: 'k1' });
await bd.surveys.submit(config.id, [{ question_id: 'q1', answer_number: 9 }], { respondentId: respondent_id });
// ─ Messaging dispatch ─ (Bearer JWT auth, not the API key)
await bd.messaging.dispatch({
projectId: 'project-uuid',
channel: 'push',
content: { title: 'Hi', body: 'There' },
targeting: { type: 'all' },
scheduling: { deliveryType: 'immediate' },
accessToken: '<supabase-session-jwt>',
});
// ─ LLM observability ─
await bd.captureTrace({
traceId: 't-1', spanId: 's-1', model: 'gpt-4o',
inputText: 'prompt', outputText: 'completion',
promptTokens: 100, completionTokens: 50, durationMs: 820, costUsd: 0.003,
});
// Flush remaining events + stop the background timer before exit.
await bd.shutdown();Configuration
| Option | Default | Description |
| ----------------- | ------------------------------ | ----------- |
| host | https://api.billdog.io/v1 | API base URL |
| flushAt | 20 | Batch size that triggers a flush |
| flushInterval | 10000 | Background flush cadence (ms) |
| maxQueueSize | 1000 | Drop oldest events past this many queued |
| gzip | true | Gzip large request bodies |
| localEvaluation | false | Evaluate feature flags locally (server-only) |
| requestTimeout | 10000 | Per-request timeout (ms) |
| maxRetries | 3 | Retry attempts for 5xx / 429 / network errors |
| enableLogging | false | Verbose diagnostics to console |
Authentication uses the X-BillDog-API-Key: <apiKey> header (bd_test_* sandbox /
bd_live_* live) on every request, except messaging dispatch, which
authenticates with a Supabase session Bearer JWT + project membership.
Analytics batching & delivery
- Events accumulate in an in-memory queue and ship as a single batched POST
to
/ingest-events. - A flush happens on
flushAt, everyflushInterval, or onflush()/shutdown(). - Failed flushes re-queue their batch (never lose events on a transient failure); the transport also retries with exponential backoff (1s, 2s, 4s).
- Bodies are gzip-compressed when large enough to benefit.
Feature flags — local evaluation
With localEvaluation: true, the SDK fetches flag definitions once
(POST /feature-flag-definitions), caches them for 5 minutes, and evaluates
deterministically on-process:
- missing/inactive →
false - ALL
targeting_rulesmust matchpersonProperties, elsefalse bucket = murmurhash3("{key}.{distinctId}") % 100; ON iffbucket < rollout_percentage- multivariate: walk
variantsby cumulative rollout within the ON bucket → variant key
The murmurhash3 (32-bit, seed 0) implementation is byte-identical across web / iOS / Android / all server SDKs, so a user buckets the same everywhere.
BillDog Auth — verifying session tokens
Verify a session token issued by BillDog Auth, in your own request handler, without calling BillDog. Verification is a local signature check against a cached JWKS, so an outage on our side cannot take your authorization with it and a hot route pays no network cost.
import { verifyToken, hasEntitlement, BillDogAuthError } from 'billdogeng-node';
app.use(async (req, res, next) => {
try {
const claims = await verifyToken(req.headers.authorization!.slice(7), {
projectId: process.env.BILLDOG_PROJECT_ID!,
environment: 'production', // defaults to 'production' — see below
});
if (!hasEntitlement(claims, 'pro')) return res.status(403).end();
req.userId = claims.sub; // the person who signed in
req.customerId = claims.cus; // the record they own, for joining BillDog data
next();
} catch (e) {
if (e instanceof BillDogAuthError) return res.status(401).end();
throw e;
}
});The environment option is a security control, not a convenience
It defaults to the strict answer, 'production'. Sandbox and production
identities are different people in BillDog, and a backend serving real users
must refuse a sandbox token even though it is genuinely signed. Set it to
'sandbox' only on a backend that serves sandbox traffic.
Exports
| | |
|---|---|
| verifyToken(token, options) | Local verification. Returns SessionClaims, throws BillDogAuthError. |
| introspect(...) | Asks the server whether the session is live right now. Costs a network call. |
| hasEntitlement(claims, key) | Entitlement check that respects truncation — see below. |
| entitlementsComplete(claims) | False when ent hit the size cap. |
| factorAge(claims) | Minutes since the first / second factor, for step-up decisions. |
| clearJwksCache() | Drop the cached key set (tests, or after an emergency rotation). |
Two properties worth knowing before you rely on this
A verified token proves it was valid when issued, not that the session is live
now. Access tokens last 90 seconds precisely so that window stays small. If you
need certainty inside it — a "sign out everywhere" that takes effect immediately
— call introspect(). Offline verification buys you a bounded staleness; that is
the trade.
Never read ent as a complete list without checking. It is capped, and
ent_truncated tells you when it was cut. Treating a truncated claim as empty
turns a paying customer into a free one. hasEntitlement() handles this for you.
What this SDK deliberately does not do
No credential handling and no UI. A server SDK that could sign users in would
need their passwords, which is exactly what BillDog Auth exists to keep off your
infrastructure. Users sign in through @billdog.io/auth
on the web or the platform SDKs on mobile; this verifies what they carry.
Build & test
npm install
npm run build # tsc → dist/
npm test # vitestLicense
MIT
