Guides
Verify media actually arrived
Green dashboards lie: a participant can join while publishing nothing. The session manifest is the ground truth — a track with a non-null start_ts carried real media.
Most video integrations are verified by hope. The join call succeeded, the participant tile rendered, nobody complained in the demo — so the integration ships. Then a customer reports that half their standup recorded silence, and nobody can say whether the browser never published, the network dropped it, or the recording pipeline lost it.
Synento gives you a ground truth to check instead: the session manifest. It lists every participant and every track that actually holds recorded media, with time bounds. A track with a non-null start_ts carried real frames; a track that is absent, or whose start_ts is null, did not. That one rule turns integration testing from watching calls into asserting on data.
This guide shows how to read the manifest after a test call, how to gate CI and post-deploy smoke tests on it, how a coding agent proves the same thing through the MCP server, and what a missing track usually means.
1. The manifest is the ground truth
A session has two kinds of state: who joined, and what media arrived. Join state is cheap and misleading — a browser can hold a WebSocket open for an hour while publishing nothing, and every dashboard will show it as connected. Media state is what your product actually sells, and it lives in the manifest at GET /v1/sessions/:id/manifest.
Each participant lists their tracks by kind — cam, mic, screen — with start_ts set the moment the first frame reached the server and end_ts when the track ended. Assert on the tracks, never on the participant list: the manifest once listed tracks that held nothing, and the fix was exactly this rule — a track is real when start_ts is non-null.
2. Read it after every test call
Make this the last step of any manual test: two browsers join, both publish camera and microphone for ten seconds, both leave, then read the manifest. Both participants should list cam and mic tracks with non-null start_ts. If screen was shared, a screen track should be there too. Anything else is a failed integration wearing a passing demo.
# Did media really arrive? Ask the recording.
curl -s https://api.synento.com/v1/sessions/$SESSION_ID/manifest \
-H "Authorization: Bearer $SYNENTO_API_KEY" \
| jq '.data.participants[] | {user_id, tracks: [.tracks[] | {kind, start_ts}]}'3. Gate CI on media, not on joins
The same check belongs in automation. After your end-to-end test drives two publishers through a session, fail the run when any expected track is missing. The script below uses @synento/node and exits non-zero with exactly what is missing, so the failure points at the publishing side instead of reading as a flaky test.
Run it in CI and as a post-deploy smoke test. Media regressions — an autoplay policy change, a broken getUserMedia constraint, a token minted for the wrong user — all surface here first, as absent tracks, long before a customer notices silence.
import { SynentoClient } from '@synento/node'
// Reads SYNENTO_API_KEY and SYNENTO_API_URL. Run after a two-person test
// call, in CI or in a post-deploy smoke test.
const synento = new SynentoClient()
const sessionId = process.argv[2]
if (!sessionId) throw new Error('usage: verify-session.mts <session-id>')
const manifest = await synento.getSessionManifest(sessionId)
const problems: string[] = []
for (const participant of manifest.participants) {
for (const kind of ['cam', 'mic'] as const) {
const track = participant.tracks.find((t) => t.kind === kind)
// start_ts is set only once media reached the track. Null means the
// participant joined but never published — a failed integration, not a
// quiet success.
if (!track || track.start_ts === null) {
problems.push(`${participant.user_id} is missing ${kind}`)
}
}
}
if (manifest.participants.length < 2) problems.push('expected two participants')
if (problems.length > 0) {
console.error(problems.join('\n'))
process.exit(1)
}
console.log(`ok: ${manifest.participants.length} participants carried cam and mic`)4. Let your agent prove it too
A coding agent wiring up your integration faces the same problem you do: it cannot watch the call. The MCP server gives it the same ground truth through verify_session. The agent names the session and the participants and tracks it expects; the tool reads the recording back and returns passed, or what is missing and what usually causes it. Treat a passing verify_session as the definition of done for integration work, and do not accept the agent's word for it before that.
When verification fails and the app looks correct, run_media_check separates the two halves: it runs a synthetic two-participant call through the real WebSocket endpoint into a fresh session and verifies that from the recording. If the synthetic call passes, the Synento side is healthy and the bug is in the app's publishing path. If it fails, the problem is underneath your code.
5. What a missing track usually means
Nine times out of ten a missing track is the app, not the platform. Work through these in order before assuming anything else.
- The token's user does not match the room's user. The token is minted for one
user_idand the browser joins as another, so the publish lands nowhere you are looking. Mint and join with the same id. - The browser cannot publish at all. Call
SynentoRoom.checkSupport()before joining: anything below Chrome/Edge 94, Safari 16.4 or Firefox 133 gets the replay path, not a broken live call. - Publish was never called, or was called on a stream with no live tracks. Confirm
getUserMediaresolved and the tracks are live beforepublishCamera. - Screen share ends when the user stops sharing. A
screentrack with an earlyend_tsusually means the picker was dismissed, not that the platform dropped it. - A participant who reconnects mints a fresh token through
getToken. A stale hardcoded token fails the reconnect and the second half of the call records nothing. - Correlate with the event timeline. Joins, leaves and track starts share one clock, so a track that starts minutes after the join tells you the user sat muted in the lobby — a UX problem, not a media one.
6. Know your three track kinds
Camera, microphone and screen behave differently, and the manifest shows it. cam and mic normally start together within a second of publishing; a mic without a cam (or the reverse) usually means one publish call failed while the other succeeded, so check both calls rather than assuming the whole join broke. screen is episodic by nature: it starts when sharing starts and ends when the user stops, so an early end_ts on screen next to healthy cam and mic is a dismissed picker, not a dropped track.
Read end_ts with the same suspicion as start_ts. A track that never ends belongs to a participant who never left cleanly — a closed laptop, a killed tab — and its media is still real up to the last frame. For the final verdict on a session, read the manifest after everyone leaves, when both bounds are settled, and assert on the pair: non-null start, end at or after start.
Common questions
What is the difference between an absent track and a null start_ts?
An absent track was never announced: the participant never published that kind. A null start_ts means the track was announced but no media ever reached it. Both fail the check; the distinction tells you whether to look at the publish call or the network path.
How soon after the call can I read the manifest?
Tracks appear as media arrives, so during a call the manifest already shows what is flowing. For a final verdict, read it after the participants leave, when end_ts values are settled.
Does this work for audio-only or screen-only sessions?
Yes. Check for exactly the tracks you expect: mic only for audio calls, screen plus mic for walkthroughs. The rule is per track, so sparse sessions verify just as well.
Can an agent verify without joining the call?
That is the point. verify_session reads the recording the app published into — the agent never joins, never watches, and never trusts its own code. It trusts the manifest.
More guides
- Record a video call without a recording serverThe 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.
- Add live video to an AI agent via the MCP serverA remote MCP server at api.synento.com/mcp takes your agent from nothing to a verified integration: projects, keys, sessions, tokens, and manifest proof.
- WebCodecs vs WebRTC for recorded callsIf the call is the product, use WebRTC. If the recording of the call is the product, the WebCodecs relay trade is worth understanding.
- Async video messages and standupsAsync video is mostly replay. Create one session per standup, let the team watch on their own time through expiring links, and export only what must be kept.
- Signed webhooks for recording-ready workflowsWebhooks tell your pipeline when the recording is ready. Verify the HMAC signature on every delivery, then fan out to transcription, review or delivery.
- Restricted keys and short-lived connection tokensTwo credentials, two jobs. A restricted key identifies your server to the API; a connection token admits one participant to one session for about an hour.
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.