@vibefollow/sdk
v1.2.1
Published
Official TypeScript SDK for Vibefollow — identify users, emit the 10 canonical lifecycle events with typed helpers, bulk-backfill existing data, batch events in-memory, and verify webhook signatures. Includes typed error classes, automatic retries with ex
Downloads
178
Maintainers
Readme
@vibefollow/sdk
Official TypeScript SDK for Vibefollow — AI follow-ups that convert SaaS signups. Identify users, emit lifecycle events, and verify outbound webhooks with a single, fully-typed client.
Install
npm install @vibefollow/sdk
pnpm add @vibefollow/sdk
yarn add @vibefollow/sdkRequires Node 20+. Works in Cloudflare Workers (with nodejs_compat), Vercel Edge, and Deno. The browser is not a supported runtime — your API key must stay server-side.
Quickstart (30 seconds)
import { VibeFollow } from '@vibefollow/sdk';
const vf = new VibeFollow({ apiKey: process.env.VIBEFOLLOW_API_KEY! });
// Upsert a user
await vf.users.identify('usr_42', {
email: '[email protected]',
name: 'Jane Doe',
plan: 'trial',
});
// Emit a lifecycle event
await vf.users.signedUp('usr_42', { plan: 'trial', source: 'organic' });That's it. The SDK takes care of authentication, idempotency, retries, and error mapping.
Authentication
API keys are issued in the Vibefollow dashboard under Settings → Developers → API keys. They follow the Stripe-style prefix scheme:
sk_live_…— productionsk_test_…— sandbox (future)
Pass the key once when constructing the client:
const vf = new VibeFollow({ apiKey: process.env.VIBEFOLLOW_API_KEY! });All requests go out over HTTPS. The key is sent in the Authorization: Bearer … header — never as a query parameter.
Lifecycle events (typed helpers)
The 10 canonical lifecycle events have first-class TypeScript helpers. Use them over events.track() whenever possible — you get autocomplete and protection against name typos.
| Helper | Event name |
| --------------------------------------- | ------------------------- |
| vf.users.signedUp(userId, props) | user_signed_up |
| vf.users.trialStarted(...) | trial_started |
| vf.users.featureUsed(...) | feature_used |
| vf.users.onboardingStep(...) | onboarding_step |
| vf.users.subscriptionChanged(...) | subscription_changed |
| vf.users.subscriptionCancelled(...) | subscription_cancelled |
| vf.users.paymentFailed(...) | payment_failed |
| vf.users.trialExpiring(...) | trial_expiring |
| vf.users.userInvited(...) | user_invited |
| vf.users.usageThresholdReached(...) | usage_threshold_reached |
Example:
await vf.users.subscriptionChanged('usr_42', {
from: 'trial',
to: 'pro',
interval: 'yearly',
mrr: 9900, // cents
});Generic tracking
For everything outside the canonical 9, use events.track():
await vf.events.track('dashboard_created', 'usr_42', {
source: 'wizard',
workspaceId: 'ws_3',
});Custom event names are accepted as-is. The API validates them server-side.
Batch tracking
For high-volume clients (e.g. backfilling historical events), use events.batch() to amortize HTTP overhead:
const batch = vf.events.batch({ maxSize: 100, maxAgeMs: 5_000 });
for (const row of historicalEvents) {
batch.track(row.name, row.userId, row.properties);
}
// Drain on shutdown.
await batch.flush();The batch auto-flushes when the queue reaches maxSize OR when maxAgeMs has elapsed since the first queued event. Call flush() explicitly to force a drain (e.g. before process exit).
Webhooks
Vibefollow sends signed webhook deliveries when emails are opened, clicked, bounced, replied to, or when users unsubscribe. Verify each delivery with webhooks.constructEvent():
import { VibeFollow, WebhookSignatureError } from '@vibefollow/sdk';
const vf = new VibeFollow({ apiKey: process.env.VIBEFOLLOW_API_KEY! });
app.post('/webhooks/vibefollow', express.raw({ type: '*/*' }), (req, res) => {
try {
const event = vf.webhooks.constructEvent(
req.body, // raw Buffer or string — NOT JSON.parse(body)
req.headers['x-vibefollow-signature'],
process.env.VIBEFOLLOW_WEBHOOK_SECRET!,
);
switch (event.type) {
case 'email.opened':
console.log('opened', event.data.draftId);
break;
case 'email.replied':
console.log('reply tone:', event.data.tone);
break;
// …
}
res.sendStatus(204);
} catch (err) {
if (err instanceof WebhookSignatureError) return res.sendStatus(401);
throw err;
}
});Why the raw body?
The signature is computed over the exact bytes the server sent. If you parse the body first and re-serialize, whitespace and key ordering changes will fail verification. Always feed the SDK the unmodified request body.
Signature header
X-Vibefollow-Signature: t=<unix_seconds>,v1=<hex_sha256>The SDK rejects any delivery whose t is outside ±5 minutes (configurable via toleranceSeconds). This is the standard Stripe-style replay window.
Errors
Every error thrown by the SDK extends VibeFollowError. Narrow to a subclass for typed recovery.
| Class | HTTP status | Retryable | Meaning |
| ----------------------- | ----------- | ------------ | ------------------------------------------------------ |
| AuthError | 401 / 403 | No | API key missing, invalid, or revoked |
| ValidationError | 422 | No | Request body failed validation; .field indicates |
| RateLimitError | 429 | Yes (auto) | Rate limited; .retryAfterMs from Retry-After |
| ServerError | 5xx | Yes (auto) | Server problem; retried with exponential backoff |
| NetworkError | 0 | Yes (auto) | DNS, refused, abort, timeout |
| WebhookSignatureError | 0 | No | HMAC mismatch or timestamp outside tolerance |
| VibeFollowError | other 4xx | No | Generic — base class for everything above |
import { AuthError, RateLimitError } from '@vibefollow/sdk';
try {
await vf.users.signedUp('usr_42');
} catch (err) {
if (err instanceof AuthError) {
console.error('check your API key');
} else if (err instanceof RateLimitError) {
console.error('still rate limited after retries; backoff', err.retryAfterMs);
} else {
throw err;
}
}Auto-retries (with exponential backoff + jitter) are applied on NetworkError, ServerError, and RateLimitError. Non-retryable errors throw on the first failure.
Configuration
new VibeFollow({
apiKey: process.env.VIBEFOLLOW_API_KEY!,
baseUrl: 'https://api.vibefollow.com', // default — override for self-host or test
timeoutMs: 10_000, // per-request timeout via AbortController
maxRetries: 2, // up to 3 total attempts on retryable errors
fetchImpl: globalThis.fetch, // inject in tests or on edge runtimes
});Every request automatically includes:
Authorization: Bearer <apiKey>Content-Type: application/jsonUser-Agent: vibefollow-sdk/<version>Idempotency-Key: <auto-uuid>(server dedupes within a 24h window)
Edge runtimes
The core (HTTP client + resource APIs) works anywhere fetch, AbortController, and crypto.randomUUID are available — that is, Node 20+, Cloudflare Workers, Vercel Edge, and Deno.
Webhook verification uses node:crypto. On Cloudflare Workers, enable the nodejs_compat flag in wrangler.toml. On pure-edge runtimes without Node compatibility, webhook verification is unsupported in v1; track this issue for a Web Crypto fallback.
Versioning
Semver. Major = breaking. The SDK version is exported as SDK_VERSION and flows into the User-Agent header on every request so the backend can flag outdated clients.
Pre-release dist-tags:
npm install @vibefollow/sdk@betaSee CHANGELOG.md for release notes.
License
MIT
