Overview

Integrating LiveKit with Roark enables automatic analysis of audio call recordings. After each call, audio recordings and related metadata are made available to Roark. Roark processes this data to evaluate assistant or participant behavior and analyze tool usage and conversation flow.

Prerequisites

Before setting up the integration, ensure you have:

  • ✅ A Roark API Key - Generate one here
  • ✅ A LiveKit account and project – Create one here
  • ✅ A storage bucket from a provider of your choice (e.g., AWS, Azure, GCP)
  • ✅ A voice assistant running on LiveKit Cloud or a self-hosted LiveKit instance
  • ✅ A webhook endpoint up and running and configured in the LiveKit dashboard settings page - LiveKit Webhook Documentation
1

Install Required Packages

npm install @roarkanalytics/sdk @aws-sdk/client-s3 @aws-sdk/s3-request-presigner livekit-server-sdk
2

Environment Setup

Configure these environment variables:

# LiveKit Configuration
LIVEKIT_URL=your_livekit_url
LIVEKIT_API_KEY=your_livekit_api_key
LIVEKIT_API_SECRET=your_livekit_api_secret

# S3 Configuration
S3_KEY_ID=your_s3_key_id
S3_KEY_SECRET=your_s3_key_secret
S3_BUCKET=your_bucket_name
S3_ENDPOINT=your_s3_endpoint
S3_REGION=your_s3_region

# Roark Configuration
ROARK_API_KEY=your_roark_api_key
3

LiveKit Recording

LiveKit does not store call recordings by default. To enable recording, you need to explicitly start an egress session using the LiveKit Egress API. This setup guide walks you through the steps required to configure recording and send audio data to Roark for analysis.

For more details on LiveKit’s Egress API, refer to the official documentation.

In this example, we’ll use AWS S3 to store the recordings. To get started, you’ll need the following:

  • Your LiveKit API credentials (API Key and Secret)
  • A valid AWS S3 bucket and AWS credentials
  • The name of the LiveKit room you want to record

Once everything is set up, you can call the Egress API to start recording and store the audio in your S3 bucket.

// Initialize LiveKit Egress client
const hostURL = new URL(process.env.LIVEKIT_URL);
hostURL.protocol = 'https:';

const egressClient = new EgressClient(
  hostURL.origin,
  process.env.LIVEKIT_API_KEY,
  process.env.LIVEKIT_API_SECRET
);

// Start recording
async function startRecording(roomName: string) {
  const fileOutput = new EncodedFileOutput({
    // Use a unique filename for each recording
    filepath: `${new Date(Date.now()).toISOString()}-${roomName}.mp4`,
    output: {
      case: 's3',
      value: new S3Upload({
        endpoint: process.env.S3_ENDPOINT,
        accessKey: process.env.S3_KEY_ID,
        secret: process.env.S3_KEY_SECRET,
        region: process.env.S3_REGION,
        bucket: process.env.S3_BUCKET,
      }),
    },
  });

  await egressClient.startRoomCompositeEgress(
    roomName,
    { file: fileOutput },
    { layout: 'speaker' }
  );
}
4

Stopping the Recording in LiveKit

Once the session ends, you should stop the recording to ensure it is properly saved to your storage provider. This action will also trigger the egress_ended webhook event, which includes the recording URL.

When stopping the egress session, make sure you reference the correct room name that was used to start the session.

For more details on available LiveKit webhook events, see the webhook documentation.

  async function stopRecording(roomName: string) {
    const hostURL = new URL(process.env.LIVEKIT_URL);
    hostURL.protocol = 'https:';

    const egressClient = new EgressClient(hostURL.origin, process.env.LIVEKIT_API_KEY, process.env.LIVEKIT_API_SECRET);

    const activeEgresses = (await egressClient.listEgress({ roomName })).filter(
      (info) => info.status < 2,
    );

    await Promise.all(activeEgresses.map((info) => egressClient.stopEgress(info.egressId)));
  }
5

LiveKit Webhook Integration

LiveKit provides webhooks that notify you about changes in the room, participants, and egress sessions. In our case, when you stop the recording using the Egress API, the egress_ended event will be triggered. This event includes the URL of the recording uploaded to your S3 storage.

The implementation for handling this webhook event is as follows:

// This example is using express.js
import { WebhookReceiver } from "livekit-server-sdk";
import express from "express";

const app = express();

// Enable raw body parsing for LiveKit webhook events
// @see https://docs.livekit.io/home/server/webhooks/#receiving-webhooks
app.use(express.raw({ type: "application/webhook+json" }));

// LiveKit webhook receiver
const receiver = new WebhookReceiver(
  process.env.LIVEKIT_API_KEY,
  process.env.LIVEKIT_API_SECRET
);

app.post("/webhook/livekit", async (req, res) => {
  const event = await receiver.receive(
    req.body,
    req.get("Authorization"),
    true
  );

  // Handle egress ended event
  if (event.event === "egress_ended") {
    const result = event.egressInfo.result;

    // S3 Uploaded File URL
    const fileUrl = result.value.filename;

    // Rest of the implementation...
  }
})

6

S3 Acccess

LiveKit does not support S3 pre-signed URLs. Therefore, once your recording is uploaded to your own S3 bucket, you should generate a signed URL yourself to provide secure, time-limited access to the file. This ensures that only authorized users can access the recording without exposing it publicly.

// S3 configuration
const s3Client = new S3Client({
  region: process.env.AWS_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  },
});

// Generate temporary S3 access URL
async function generateSignedUrl(fileKey: string) {
  const command = new GetObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: fileKey,
  });

  return await getSignedUrl(s3Client, command, { 
    expiresIn: 3600 // 1 hour access
  });
}
7

Get participants info from LiveKit

To send a call to evaluate using Roark, you need participant information from the session. You can retrieve participant details by using the LiveKit SDK.

import { RoomServiceClient } from "livekit-server-sdk";

async function getParticipants(roomName) {
  const roomService = new RoomServiceClient(
    process.env.LIVEKIT_URL,
    process.env.LIVEKIT_API_KEY,
    process.env.LIVEKIT_API_SECRET
  );

  const participants = await roomService.listParticipants(roomName);

  return participants;
}
8

Send call to evaluate using Roark

Final step, once you have gathered all the necessary data, map it to the format required by the Roark SDK before sending. Below is an example of what the complete implementation should look like.

import Roark from "@roarkanalytics/sdk";

// Initialize Roark
const roark = new Roark(process.env.ROARK_API_KEY);

// Your LiveKit webhook endpoint
app.post("/webhook/livekit", async (req, res) => {
  const event = await receiver.receive(
    req.body,
    req.get("Authorization"),
    true
  );

  // Handle egress ended event
  if (event.event === "egress_ended") {
    const result = event.egressInfo.result;
    // S3 Uploaded File URL
    const fileUrl = result.value.filename;

    // Generate signed URL for S3 file
    const signedUrl = await generateSignedUrl(fileUrl);

    // Get list of participants
    const participants = await getParticipants(event.egressInfo.roomName);

    const partipants = [];

    for (const participant of participants) {
      partipants.push({
        role: participant?.kind === 0 ? "AGENT" : "CUSTOMER",
        name: participant?.name,
        spokeFirst: participant?.kind === 0,
        phoneNumber: participant?.sip?.phoneNumber,
      });
    }

    // Create an evaluation for a single call
    await roark.evaluation.createJob({
      /**
       * You can find the evaluator slug from the Evaluators section on the dashboard
       * Or you can use 'all' to evaluate with all available evaluators.
       *
       * Evaluators to evaluate the call
       * Usage examples:
       * - Specific evaluators: evaluators: ['evaluator-slug-1', 'evaluator-slug-2']
       * - All evaluators: evaluators: 'all'
       */
      evaluators: ["evaluator-slug-1", "evaluator-slug-2"],
      call: {
        recordingUrl: signedUrl,
        startedAt: new Date().toISOString(),
        callDirection: "INBOUND",
        interfaceType: "PHONE",
        participants: partipants,
      },
    });
  }

  res.sendStatus(200);
});

🎉 You’re all set! Your LiveKit assistant is now integrated with Roark.

Need help? Contact Roark Support