Quickstart: Server (Node)
Send events your backend owns from Node with a secret key.
Send an event your server knows about, and see it on the contact in FounderHQ. About 5 minutes.
Before you start
You need a FounderHQ account and a secret key. In the app, open Sequences → Settings → Event API keys, create a key for Backend, and copy it. The full key is shown only once. Keep it on your server, never in a browser or a mobile app.
Use the Node SDK for facts only your backend can prove: a signup that passed validation, a plan change, a webhook you received. Use a client SDK for what people do in the interface.
Install the package
npm install @founderhq/events-nodeCreate one client for your process
// lib/analytics.ts
import { FounderHqNode } from "@founderhq/events-node";
export const events = new FounderHqNode(process.env.FOUNDERHQ_SECRET_KEY!);Put FOUNDERHQ_SECRET_KEY=fhq_sk_XXXX in your server environment. The
client refuses a publishable key, so a mix-up fails at startup rather
than in production.
Capture a server-truth event
Every server event names the contact it belongs to. Pass your own user ID, an email, or a phone number — at least one is required.
events.capture({
contact: { externalId: "user_8421", email: "jane@acme.com" },
event: "subscription.upgraded",
properties: { plan: "growth", mrr: 99 },
});Use the same externalId you pass to identify in the browser or the
app. That is what keeps one contact instead of two.
Retrying a webhook? Pass a stable idempotencyKey so the same delivery
counts once.
events.capture({
contact: { externalId: "user_8421" },
event: "subscription.upgraded",
properties: { plan: "growth" },
idempotencyKey: "stripe_evt_1a2b3c",
});Flush before the process ends
The client batches in the background. Serverless functions and scripts exit too fast for that, so flush explicitly.
await events.flush();Call await events.shutdown() when your server stops, to send what is
left and stop the timer.
Verify
- Run the code path that captures the event.
- In FounderHQ, open Sequences → Contacts and open Jane.
- You see
subscription.upgradedwithplan: growthon her timeline. - Sequences → Events shows the same event in the live stream.
Troubleshoot
The event never arrives
Pass an onError handler when you create the client. It reports every
rejected or dropped event with the reason.
export const events = new FounderHqNode(process.env.FOUNDERHQ_SECRET_KEY!, {
onError: (error, dropped) => console.error(error, dropped),
});A second contact appeared for the same person
Your server and your client are using different IDs. Both must use the same stable user ID. See Identity and contacts.
An event starting with $ is ignored
Names that start with $ are reserved for the SDKs. Send your own
name instead. Revenue has its own API — see Revenue.