Skip to content

Guides

Record a video call without a recording server

The live streams are the recording. Create a session, publish from the browser, then replay or export — with no recording pipeline to run or pay for separately.

Recording a video call the conventional way means running two pipelines: one that carries the live conversation, and a second that captures it. The second pipeline is a recorder bot that joins every call, a compositing service that mixes the tiles, an encoding step, a storage bucket, and a second bill metered per recorded minute. Each piece is a thing that can fail silently: the bot never joins, someone forgets to press record, the composite finishes an hour later.

Synento collapses the two pipelines into one. Browsers encode camera, microphone and screen with WebCodecs and send the frames over a WebSocket; the server relays each participant's tracks into durable streams as they pass. Those streams are the live call and the recording at the same time, so there is no recording server, no start or stop call, and no processing delay before replay.

This guide builds a recorded call end to end: create a session on your server, join from the browser with @synento/client, replay the call the moment it ends, and export an MP4 when someone needs a file to keep.

1. The two-pipeline problem

On a WebRTC platform, the media path is optimized for one thing: the lowest possible latency between participants. Recording is a second concern bolted on afterwards. A recorder joins the call as a phantom participant, subscribes to every track, composites them into one picture, encodes it, and uploads the result. You pay for the call minutes and then again for the recording minutes, and the recording exists only if every stage worked.

The failure modes are all quiet. A missed start-recording call loses the session. A recorder that crashes mid-call produces a truncated file. A composite that queues behind other jobs arrives after the user who needed it gave up. Each of these looks like success from the live call's side, which is why teams learn to verify recordings separately — if they verify them at all.

2. How stream-native recording works

With Synento there is exactly one media path. Each browser publishes its tracks — camera, microphone, screen — as binary frames over a single WebSocket. The server is a codec-agnostic relay: it never decodes payloads, it appends each participant's frames to that participant's durable streams, keyed by track. Camera, microphone and screen stay separated, timestamped on one clock.

Because the streams are durable the moment frames arrive, the session is recorded by construction. Ending the call changes nothing about storage; replay reads the same streams the live call just wrote. There is no recording flag to set, no bot to invite, and no encode queue between the call and its replay.

  • Each participant's camera, microphone and screen are separate tracks, so review, transcription and redaction never need to unmix anything.
  • Replay is available the moment the call ends: nothing encodes, composites or uploads first.
  • Recording has no rate of its own. You pay participant-minutes for the call and storage for keeping it, and nothing for the act of recording.

3. Create the call on your server

Sessions are created server-side with @synento/node, which holds your secret key. The browser never sees the key: it gets a short-lived connection token per participant, minted by the route below. Pass an Idempotency-Key when retries must not create the session twice.

app/api/synento/route.ts
import { SynentoClient, resolveWsUrl } from '@synento/node'

// Reads SYNENTO_API_KEY and SYNENTO_API_URL. Server-side only.
const synento = new SynentoClient()

// There is no recording to start: the session below is already being
// recorded while it is live.
export async function createRecordedCall(name: string) {
  const session = await synento.createSession({ name })
  return session.id
}

// One token route serves every participant. Tokens are short-lived and
// scoped to a single session and user.
export async function POST(request: Request) {
  const { sessionId, userId } = await request.json()
  const token = await synento.createConnectionToken(sessionId, { userId })

  return Response.json({
    token: token.accessToken,
    wsUrl: resolveWsUrl({
      apiBaseUrl: process.env.SYNENTO_API_URL!,
      requestOrigin: request.headers.get('origin'),
      sessionId,
      userId,
    }),
  })
}

4. Join from the browser

The browser joins with @synento/client. SynentoRoom manages the WebSocket, publishes local tracks, decodes remote participants, and reconnects with a freshly minted token when the connection drops. The two publish calls below are the entire recording integration: from this point on, everything the participants say and show is durably stored.

call.ts
import { SynentoRoom } from '@synento/client'

const room = new SynentoRoom({
  userId: 'alice',
  // Re-invoked on every reconnect, so tokens rotate on their own.
  getToken: () =>
    fetch('/api/synento', {
      body: JSON.stringify({ sessionId, userId: 'alice' }),
      method: 'POST',
    }).then((res) => res.json()), // { wsUrl, token }
})

room.on('participantsChanged', (ids) => {
  // Render a tile per id; attach each with room.attachParticipant(id, canvas).
})

await room.connect()

const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true })
await room.publishCamera(stream)
await room.publishMicrophone(stream)
// The call above is the recording. Nothing else to start, stop or upload.

5. Replay the moment the call ends

When the last participant leaves, the recording is already complete — there is no processing step to wait for. Your app hands viewers a replay the same way it handed participants a live call: mint a token with replay: true, optionally with from_ts to start at a moment and speed to skim, or create an expiring replay link with createReplayLink for anyone with the URL.

Playback reads per-participant tracks through the playback API, and @synento/player composes them in grid, presenter or active-speaker layouts. Windowed playback jumps straight to a timestamp, which is what review and support workflows build on.

6. Export an MP4 when you need a file

Some destinations still want a file: a research repository, a customer deliverable, long-term storage past retention. One request renders the session's tracks into an MP4 with the layout, orientation and focus you choose. Identical requests return the existing export instead of encoding again.

terminal
# Ask for an MP4. The same options return the same export, not a re-encode.
curl -s -X POST "https://api.synento.com/v1/sessions/$SESSION_ID/export?layout=grid&orientation=landscape" \
  -H "Authorization: Bearer $SYNENTO_API_KEY" | jq .data

# Poll the job until it renders, then download the file.
curl -s https://api.synento.com/v1/exports/$JOB_ID \
  -H "Authorization: Bearer $SYNENTO_API_KEY" | jq .data.status
curl -o standup.mp4 https://api.synento.com/v1/exports/$JOB_ID/download \
  -H "Authorization: Bearer $SYNENTO_API_KEY"

7. Retention, then export

Recordings are kept per project: up to 30 days on Free and 90 days on Pro. Export the sessions you must keep before retention expires, and let the rest fall away on their own. When one participant asks to be forgotten, redact just their tracks with DELETE /v1/sessions/:id/participants/:userId instead of destroying the whole session.

8. What it costs

There is no recording rate because there is no recording product. A call costs participant-minutes — one person in a session for one minute — plus storage for what you keep. Replay and MP4 export have their own allowances past which Pro pays overage: 1,500 replay minutes and 300 export minutes on Free, 15,000 and 3,000 on Pro. See the pricing page for the current numbers.

Storage is the number teams underestimate. A stored participant-minute at the default 720p takes roughly a few megabytes of camera and mic — less for audio-heavy calls, more for screen share — so an hour-long four-person call lands in the low single gigabytes. Ten gibibytes on Free holds weeks of standups; when it does not, the pricing estimator turns your cadence into a monthly number before you commit.

9. Redact one participant, keep the session

Sooner or later someone asks to be forgotten: a candidate, a customer, a teammate who shared the wrong screen. Deleting the whole session punishes everyone else in it. Redaction removes one participant's audio and video from the stored tracks instead, and tells you which earlier MP4 exports still show them — exports made before the redaction are left alone but never reused, and their ids come back so you can delete those files too.

Treat redaction as part of retention, not an exception to it. Set the retention window that matches your policy, export what must be kept, and redact on request within the window. After retention expires there is nothing left to redact, which is the correct answer to most data requests: the data is simply gone.

Common questions

Do I ever call start or stop recording?

No. There is no such call. Every session is stored as it happens; the only decisions you make are how long to keep it (retention) and whether to export an MP4.

When is the replay available?

Immediately when the call ends. Nothing encodes or uploads first, so a viewer can open a replay link seconds after the last participant leaves.

What do I get when I export?

An MP4 composed from the session's separated tracks, in grid or presenter layout, landscape or vertical, optionally focused on one participant. The same options return the same file rather than encoding twice.

What if a participant never publishes?

Then their track never reaches the server and the manifest shows it: the track is absent or its start_ts is null. Read the manifest after test calls so a quiet failure never looks like success.

Give your agent a video platform it can verify.

Sign up, point your agent at llms.txt, and have a recorded call running before lunch.