TypeScript SDK.
One package, three functions. init once, track anywhere, flush when you can't lose events. The SDK handles auth, batching, and retries on transient errors.
Install
One dep, one env var.
$ pnpm add @ingestlayer/sdk # or: npm install @ingestlayer/sdk · yarn add @ingestlayer/sdk # .env INGESTLAYER_KEY=il_live_***
Works in Node 18+, Bun, Deno, Cloudflare Workers, and modern browsers — no dependencies, uses global fetch. Pass your key to init({ apiKey }) at startup; read it from INGESTLAYER_KEY or wherever you keep secrets.
track()
Init once, then track.
import { init, track } from "@ingestlayer/sdk";
init({ apiKey: process.env.INGESTLAYER_KEY! });
track({
type: "user.signup",
icon: "🎉", // optional — shown next to the event
payload: {
email: user.email,
plan: user.intendedPlan,
source: "marketing-site",
},
});track(event) enqueues one event and returns immediately — delivery happens on the next flush (by batch size, timer, or an explicit flush()). The event shape is { type, icon?, payload?, idempotencyKey?, timestamp?, entity? }.
track({
type: "order.placed",
icon: "💸", // emoji glyph for the dashboard + valve app
payload: { order_id: o.id, amount: o.amount, currency: o.currency },
idempotencyKey: o.id, // dedupe at the gate
timestamp: o.created_at, // backdate to when it happened
entity: { kind: "account", id: o.account_id }, // enrichment hint
});icon is an optional emoji shown next to the event in the dashboard and the valve app. idempotencyKey dedupes at the gate over the configured window — usually 24h. timestamp overrides the received_at; defaults to now(). entity is an enrichment hint pipelines can match on.
flush()
Batch, then guarantee.
import { track, flush } from "@ingestlayer/sdk";
for (const u of newUsers) {
track({ type: "user.signup", payload: { email: u.email } });
}
// track() buffers and flushes on size/timer; await flush() to
// guarantee delivery (e.g. at the end of a cron job or before exit).
await flush();Events batch automatically (default: 50 events or 2s). On Node and in the browser the SDK installs a best-effort flush on shutdown, but the only hard guarantee is an awaited flush() — call it in cron jobs, imports, or any path where losing events is unacceptable.
Errors
Failures surface, don't throw.
import { init } from "@ingestlayer/sdk";
init({
apiKey: process.env.INGESTLAYER_KEY!,
onError: (err, events) => {
// Called for every batch that ultimately fails after retries.
// Persist offline, alert, or drop — your call.
console.error("ingestlayer dropped", events.length, err.message);
},
});Because track is fire-and-forget, delivery failures don't throw at the call site. The SDK retries transient errors (5xx, 408, 429) with exponential backoff; a batch that still fails is handed to your onError callback so you can persist offline or alert. 4xx client errors aren't retried.
Browser
The same call, with caveats.
The SDK works in browsers, but you probably don't want your INGESTLAYER_KEY in client bundles. Use a thin server proxy that forwards authenticated calls, or scope keys narrowly per-environment and accept the visibility tradeoff.