Package Exports
- @cybin/client
Readme
@cybin/client
Typed Node.js client for Cybin — event tracking and contact management for transactional email funnels.
Install
npm install @cybin/client
# or
pnpm add @cybin/client
# or
bun add @cybin/clientRequires Node 18+ (for native fetch).
Quick start
import { createCybinClient } from "@cybin/client";
const cybin = createCybinClient({
apiKey: process.env.CYBIN_API_KEY!,
});
// Fire a typed event — compile-time checked
await cybin.track.plantSignup({
userId: "user-123",
email: "derek@example.com",
firstName: "Derek",
trialEndsAt: new Date("2026-05-08"),
dashboardUrl: "https://plantapp.co/app",
});Every typed track.* helper:
- Enforces required merge tags at compile time. Forget
firstName→ red squiggle. - Auto-serializes Dates to ISO 8601 strings for you.
- Uses the correct event name (
plant.signup, etc.) — no typos. - Returns
{ ok: true, eventId }on success.
Typed events
// Trial signup
await cybin.track.plantSignup({
userId, email, firstName, trialEndsAt, dashboardUrl,
});
// Trial ending in 3 days
await cybin.track.trialEndingSoon({
userId, email, firstName, trialEndsAt, dashboardUrl,
});
// Trial expired without conversion
await cybin.track.trialExpired({
userId, email, firstName, dashboardUrl,
});
// One-time purchase
await cybin.track.purchaseCompleted({
userId, email, firstName,
amount: 4900, // cents
currency: "USD",
receiptUrl: "https://...",
});
// Subscription renewed
await cybin.track.subscriptionRenewed({
userId, email, firstName,
amount: 4900,
currency: "USD",
nextBillingDate: new Date("2026-06-08"),
});
// Payment failed
await cybin.track.paymentFailed({
userId, email, firstName,
updatePaymentUrl: "https://plantapp.co/billing",
});
// Subscription cancelled
await cybin.track.subscriptionCancelled({
userId, email, firstName,
accessUntil: new Date("2026-06-08"),
});Untyped / custom events
await cybin.track.event({
userId: "user-123",
email: "derek@example.com",
event: "custom.thing_happened",
properties: { foo: "bar", count: 3 },
});Identify (set user traits without firing an event)
await cybin.identify({
userId: "user-123",
email: "derek@example.com",
name: "Derek Howlett",
traits: { plan: "pro", referredBy: "twitter" },
});Newsletter subscribe
await cybin.subscribe({
email: "derek@example.com",
listId: "newsletter",
source: "homepage-footer",
});Error handling
import {
CybinAuthError,
CybinValidationError,
CybinNetworkError,
} from "@cybin/client";
try {
await cybin.track.plantSignup({ /* ... */ });
} catch (err) {
if (err instanceof CybinAuthError) {
// API key is bad. Rotate it.
} else if (err instanceof CybinValidationError) {
// 4xx — payload problem. Check err.body for details.
} else if (err instanceof CybinNetworkError) {
// Retries exhausted. Queue for later or drop.
} else {
throw err;
}
}4xx errors are not retried (they're permanent). 5xx and network errors are retried with exponential backoff — by default up to 3 total attempts.
Options
createCybinClient({
apiKey: "cyb_sk_live_...",
// Optional — defaults to https://www.cybin.app
baseUrl: "https://staging.cybin.app",
// Optional — set false in tests to make every call a no-op
enabled: process.env.NODE_ENV !== "test",
// Optional — override fetch (for polyfills, tracing, etc.)
fetch: myCustomFetch,
// Optional — retry budget for 5xx / network errors. Default: 2
maxRetries: 3,
});Getting an API key
Visit https://www.cybin.app/settings/api-keys and create one. The full key is only shown at creation — save it somewhere safe (1Password, env var, etc.).
License
MIT