@rill/server
Node SDK: create calls, mint tokens, verify webhooks.
Mint tokens on your server. The API secret never goes to the browser. Walkthrough: Your app.
npm i @rill/serverUntil a v* tag exists, use this repo’s sdk/ workspace.
import { Rill } from "@rill/server";
const rill = new Rill({
url: "https://rill.example.com",
apiKey: process.env.RILL_API_KEY!,
apiSecret: process.env.RILL_API_SECRET!,
});
const call = await rill.calls.create();
const token = call.token({
id: "alice",
name: "Alice",
publish: { microphone: true, camera: true, screen: false, data: true },
ttl: "15m",
});
console.log(call.id, token);Create a call
new Rill(opts: RillOptions)
rill.calls.create(opts?: { recording?: boolean }): Promise<Call>
call.id: string
call.token(opts: TokenOptions): string
rill.calls.token(callId: string, opts: TokenOptions): string
call.end(): Promise<void>
type RillOptions = {
url: string; // SFU origin, or operator Caddy that reverse-proxies `/v1`. Not the try-it site (`:3000...
apiKey: string;
apiSecret: string; // HS256 secret. Join tokens and join/leave webhook HMAC. Never send to the browser.
fetch?: typeof fetch; // Injected in tests. Defaults to global `fetch`.
};
type TokenOptions = {
id: string; // Opaque participant id (`sub`).
name?: string; // Optional display name (`nam` on the wire).
publish: Publish;
ttl?: string | number; // Duration string (`15m`, `1h`) or seconds. Default `15m`. Max 24h.
};
type Publish = {
microphone: boolean;
camera: boolean;
screen: boolean;
data: boolean;
};new Rill—url,apiKey,apiSecret. Never construct this in the browser.rill.calls.create—POST /v1/calls.{ recording: true }asks the sidecar to arm; sidecar down →recording: falseand 201 still.call.id— Persist this; mint later withrill.calls.token(id, opts). Nourlon the JSON.call.token— Local HS256 for this call id. Same asrill.calls.token(this.id, opts).rill.calls.token— Local HS256 JWT. Does not call the SFU.publishrequires all four keys. Default TTL 15m, max 24h.call.end— Destroy this call.Publish— All four keys required.datagates send; receive always works.
Look up, kick, mute
rill.calls.get(id: string): Promise<Call>
rill.calls.list(): Promise<Call[]>
rill.calls.participants(id: string): Promise<Participant[]>
rill.calls.kick(id: string, participantId: string): Promise<void>
call.unpublish(opts: { participantId: string; media: MediaKind }): Promise<void>rill.calls.get—GET /v1/calls/:id.rill.calls.list—GET /v1/calls. In-memory list on this process.rill.calls.participants—GET .../participants.rill.calls.kick—DELETE .../participants/:id. Emitsparticipant.left.call.unpublish— Force-disable one media kind on a participant (POST .../unpublish).
Recording
call.recording: boolean
call.recordingStart(): Promise<RecordingStart>
call.recordingStop(): Promise<RecordingStop>call.recording— Sidecar armed for this call. False if recording was requested but the sidecar was down.call.recordingStart—POST .../recording/start. Sidecar down →recording_unavailable. The live call does not wait on S3.call.recordingStop—POST .../recording/stop. Mux + upload happen on the sidecar; thenrecording.stopped.
Webhooks
verifyWebhook(opts: VerifyWebhookOpts): VerifyWebhookResult
type VerifyWebhookOpts = {
secret: string; // `RILL_API_SECRET` (join/leave) or `RILL_RECORD_WEBHOOK_SECRET` (recording.stopped).
timestamp: string; // `X-Rill-Timestamp` (unix seconds).
signature: string; // `X-Rill-Signature` (hex HMAC-SHA256 of `timestamp.body`).
body: string | Buffer | Uint8Array; // Raw POST body. Do not `JSON.parse` first.
maxSkewSeconds?: number; // Max |now - timestamp| in seconds. Default 300.
nowSeconds?: number; // Injected clock for tests.
};
type VerifyWebhookResult = { ok: true } | { ok: false; code: "unauthorized" | "timestamp" };verifyWebhook— Verify Rill webhook HMAC over the raw POST body. Afterok,JSON.parseand persistid(treat duplicateidas idempotent).
Errors
type ErrorCode =
| "unauthorized"
| "forbidden"
| "call_not_found"
| "call_full"
| "call_ended"
| "participant_invalid"
| "token_expired"
| "overloaded"
| "server_shutdown"
| "unsupported"
| "recording_unavailable"
| "recording_active"
| "permission_denied"
| "connection_failed"
| "internal";ErrorCode— Public errorcodestrings. Match the Rill HTTP JSON{ error: { code, message } }.