Quickstart

Add Relay to a web app: one backend route, a few lines of client code, then your first event.

1. Get access and keys

The SDK is in early access and is not yet on npm. Request SDK access and we will send you the package and a set of keys for each environment.

KeyWhere it livesWhat it can do
pk_dev_… / pk_live_…Your frontendIdentify your app and read consent text. Safe to embed.
rsk_dev_… / rsk_live_…Your backend onlyMint attestations for signed-in users. Shown once. Never ship it to a browser.

Tell us the origins your site runs on so we can add them to the environment's allowed origins.

.env
NEXT_PUBLIC_RELAY_PUBLIC_KEY="pk_live_…"   # public: safe in the browser
NEXT_PUBLIC_RELAY_ENDPOINT="https://…"     # sent with your keys
RELAY_SECRET_KEY="rsk_live_…"              # server only

2. Install

terminal
pnpm add @relay/sdk

The package has two entry points: @relay/sdk for the browser and @relay/sdk/server for your backend.

3. Add an attestation route

A browser cannot keep a secret, so your backend vouches for the user. After authenticating its own session, it asks Relay for a single-use attestation and returns it to the SDK. Pass a stable user ID as subject; Relay stores only an HMAC of it, salted per app.

app/api/relay/attest/route.ts
// app/api/relay/attest/route.ts: runs on YOUR backend
import { createRelayServer } from "@relay/sdk/server";
import { getSession } from "@/lib/auth"; // your own auth

const relay = createRelayServer({
  secretKey: process.env.RELAY_SECRET_KEY!,
  endpoint: process.env.NEXT_PUBLIC_RELAY_ENDPOINT!,
});

export async function POST() {
  const session = await getSession();
  if (!session) return new Response("Unauthorized", { status: 401 });
  // Vouch for this signed-in user. Relay stores only an app-scoped HMAC.
  const { attestationToken } = await relay.attest({ subject: session.userId });
  return Response.json({ attestationToken });
}

4. Initialise the client and ask

relay.ts
import { createRelay, type PhotoEditEvents } from "@relay/sdk";

export const relay = createRelay<PhotoEditEvents>({
  publicKey: process.env.NEXT_PUBLIC_RELAY_PUBLIC_KEY!,
  endpoint: process.env.NEXT_PUBLIC_RELAY_ENDPOINT!,
  // Called only after the user opts in (or to resume an earlier opt-in).
  attest: async () => {
    const res = await fetch("/api/relay/attest", { method: "POST" });
    return (await res.json()).attestationToken;
  },
});

await relay.init();                         // no network unless previously opted in
const choice = await relay.presentConsent(); // "accepted" | "declined" | "dismissed"

const session = relay.startSession();       // null unless opted in
session?.track("edit.adjustment_changed", { control: "contrast", from: 0, to: 18 });

await relay.withdraw();        // stop collection, clear queue, revoke tokens
await relay.requestDeletion(); // delete contributions from Relay
Nothing happens before opt-in

init() makes no request unless the user opted in earlier. track() returns false and stores nothing in every state except active.

5. Send events

Call startSession() when a meaningful piece of work begins, then track() each approved action. Sessions add a sessionId and sequence so Relay can reconstruct what happened in order. Events are batched and sent every 5 seconds or 20 events, whichever comes first.

Which events and fields you can send depends on the schema you selected. See Event schemas.

6. Offer withdrawal

Give users a way to change their mind, for example in account settings. relay.withdraw() stops collection and clears the queue; relay.requestDeletion() also deletes what Relay holds. See Withdrawal and deletion.