Getting started
From a ClientID and a secret to a recorded Event in four calls. You need a Throughline account; everything else is on this page.
There is no test mode. An Event you record here is real, and a Journey listening for it will email a real person. Follow this page against a staging Tenant, not your production one.
1. Create an API key
In the app, open Settings → API keys and create a Secret key. Leave the capabilities at their default, Write events and Write contacts; that is all this page needs.
The app shows the secret once. Copy both values into your environment:
export THROUGHLINE_CLIENT_ID=…
export THROUGHLINE_CLIENT_SECRET=…
2. Exchange the key for an access token
- curl
- @throughlinehq/server
curl -X POST https://server.api.throughline.dk/v1/token \
-u "$THROUGHLINE_CLIENT_ID:$THROUGHLINE_CLIENT_SECRET"
{ "accessToken": "eyJhbGciOi…", "tokenType": "Bearer", "expiresIn": 900 }
Keep the token for the next two calls:
export THROUGHLINE_TOKEN=eyJhbGciOi…
npm install @throughlinehq/server
import { ThroughlineClient } from '@throughlinehq/server';
const throughline = new ThroughlineClient({
clientId: process.env.THROUGHLINE_CLIENT_ID!,
secret: process.env.THROUGHLINE_CLIENT_SECRET!,
});
The client exchanges the key on its first call and again whenever the token expires. You never handle the token yourself.
The token is valid for 15 minutes. Authentication covers what to do when it runs out.
3. Create the Contact
Every Event is about someone, so the Contact has to exist first. Key it by the id your own system already uses for that person.
- curl
- @throughlinehq/server
curl -X PUT https://server.api.throughline.dk/v1/contacts/user-42 \
-H "Authorization: Bearer $THROUGHLINE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "email": "ada@example.com", "name": "Ada Lovelace" }'
The answer is 201 Created the first time and 200 OK on every later call, so it is safe to send on every sign-up and every profile change.
await throughline.contacts.upsert({ externalId: 'user-42', email: 'ada@example.com', name: 'Ada Lovelace' });
4. Record an Event
- curl
- @throughlinehq/server
curl -X POST https://server.api.throughline.dk/v1/events/by-external-id \
-H "Authorization: Bearer $THROUGHLINE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contactExternalId": "user-42",
"eventName": "trial_started",
"occurredAt": "2026-10-03T12:00:00Z",
"dedupKey": "trial-started-user-42",
"properties": { "plan": "pro" }
}'
{ "accepted": 1 }
202 Accepted means the Event is in. Send the same dedupKey if you retry, and a request that reached us twice is recorded once.
await throughline.events.track({
eventName: 'trial_started',
contactExternalId: 'user-42',
properties: { plan: 'pro' },
});
The client sets occurredAt and a DedupKey for you, and retries with the same DedupKey.
The first time Throughline sees an EventName, it adds it to the list a marketer picks Journey triggers from. Pick names you are happy to keep.
Next
- Errors: what each failure means and which ones to retry.
- Scopes: what else a key can be allowed to do.
- Coming from Segment or PostHog, if you are replacing one.