tridaPlatform developers

Getting started in ten minutes

You will check your key, register a webhook endpoint and receive a signed event that you verify. Nothing here touches a real provider.

What exists today. Core (projects, keys, webhook endpoints, usage) is live. Pay, Notify, Billing, AI and Account are not shipped yet, so there is no payment to create yet: the test event below travels the exact path a real event will (outbox, signed delivery, retries), so what you build now keeps working when capabilities arrive.

1. Get a project and a test key

A project and its keys are created by the platform administration (there is no self-service yet): ask the Trida team for a project in test mode and a key that starts with tp_test_. A test key only ever reaches the sandbox. Keep it secret: it opens your project.

2. Install the SDK

The SDK is published on GitHub Packages as @tridafactory/sdk. Create a GitHub token with the read:packages scope and put this in your project's .npmrc:

@tridafactory:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN_READ}

Then bun add @tridafactory/sdk (or npm install @tridafactory/sdk). You can also call the HTTP API directly with any client.

3. Check your key

import { createClient } from '@tridafactory/sdk';

const trida = createClient({
  baseUrl: 'https://api-dev.trida.lu',
  apiKey: process.env.TRIDA_API_KEY!,
});
const project = await trida.projects.me();
console.log(project.slug, project.mode); // "your-project" "test"

A wrong or revoked key answers 401 with a stable error code (API_KEY_INVALID, API_KEY_REVOKED).

4. Receive a webhook

Your receiver must verify the signature on the raw body. This function is framework independent (and is compiled and tested in the repository):

import { verifyWebhook } from '@tridafactory/sdk';

/**
 * Receive a Trida webhook: verify the signature on the RAW body, then act. Framework independent: pass the raw body exactly
 * as received (never a parsed and re-serialized JSON), the request headers, and the secrets you know.
 *
 * `secrets` holds ONE secret normally, TWO during a rotation (the new one and the previous one): either signature is accepted.
 */
export interface WebhookResult {
  status: number;
  body: string;
}

export function handleTridaWebhook(
  rawBody: string,
  headers: Record<string, string | string[] | undefined>,
  secrets: string[],
  seenEventIds: Set<string>,
): WebhookResult {
  const verification = verifyWebhook(rawBody, headers, secrets);
  if (!verification.valid) {
    // 400, not 401: the sender is Trida, and a retry with the same bytes would fail the same way.
    return { status: 400, body: `invalid signature: ${verification.reason}` };
  }

  // The same event can arrive more than once (delivery is at least once): deduplicate on the event id.
  const eventId = verification.eventId;
  if (eventId !== null && seenEventIds.has(eventId)) {
    return { status: 200, body: 'already processed' };
  }
  if (eventId !== null) seenEventIds.add(eventId);

  const event = JSON.parse(rawBody) as {
    id: string;
    type: string;
    createdAt: string;
    data: unknown;
  };
  switch (event.type) {
    case 'core.webhook.test':
      console.log('Trida test event received', event.id);
      break;
    default:
      console.log('event', event.type, event.id);
  }
  // Any 2xx tells Trida the event is delivered. Anything else is retried (1 min, 5 min, 30 min, 2 h, 8 h, 24 h...).
  return { status: 200, body: 'ok' };
}

Mount it with Express, with the raw body parser ahead of any JSON parser:

import { handleTridaWebhook } from './verify-webhook.js';

/**
 * The structural part of Express this example needs, so the file compiles without the framework. With Express itself, mount it
 * with the RAW body parser, ahead of any JSON parser:
 *
 *   import express from 'express';
 *   const app = express();
 *   app.post('/webhooks/trida', express.raw({ type: 'application/json' }), tridaWebhook);
 */
interface Req {
  body: Buffer;
  headers: Record<string, string | string[] | undefined>;
}
interface Res {
  status(code: number): { send(body: string): void };
}

const secrets = [process.env.TRIDA_WEBHOOK_SECRET ?? ''];
const seen = new Set<string>(); // use a database or a cache in production

export function tridaWebhook(req: Req, res: Res): void {
  const result = handleTridaWebhook(req.body.toString('utf8'), req.headers, secrets, seen);
  res.status(result.status).send(result.body);
}

Your endpoint must be reachable at a public HTTPS address (a tunnel such as cloudflared or ngrok works for local development): the platform refuses private addresses, plain http and redirects, so it can never be turned against an internal service.

5. Register the endpoint and send yourself an event

import { createClient } from '@tridafactory/sdk';

/**
 * Ten minutes: check your key, register an endpoint, send yourself a signed test event.
 * Run it with `TRIDA_API_KEY=tp_test_... WEBHOOK_URL=https://your-tunnel.example/webhooks/trida bun quickstart.ts`.
 */
const trida = createClient({
  baseUrl: process.env.TRIDA_API_URL ?? 'https://api-dev.trida.lu',
  apiKey: process.env.TRIDA_API_KEY ?? '',
});

// 1. Your project: the key says which one it is.
const project = await trida.projects.me();
console.log(`project ${project.slug} (${project.mode} mode)`);

// 2. Register an endpoint. The secret is returned ONCE: store it now.
const endpoint = await trida.webhookEndpoints.create({
  url: process.env.WEBHOOK_URL ?? '',
  eventTypes: ['core.webhook.test'],
});
console.log('endpoint', endpoint.id, 'secret', endpoint.secret);

// 3. Send yourself a signed test event: it travels the path of a real event.
const sent = await trida.webhookEndpoints.test(endpoint.id);
console.log('test event queued', sent.eventId);

The secret (whsec_...) is returned once: store it in TRIDA_WEBHOOK_SECRET for your receiver. Within a few seconds your endpoint receives a core.webhook.test event, signed with that secret. The Trida-Signature header carries t=<timestamp>,v1=<hmac>; verifyWebhook checks it, refuses anything older than five minutes and accepts the two secrets of a rotation.

Next