TalentScope: The Interview Platform Built from Three Backends

Clerk handles identity, Convex keeps the room in sync, and Stream carries the call. The interesting part is how cleanly the handoffs work.

9 min read · evinjohnn/TalentScope

A wide editorial scene shows three stations connected by thin lines on a white stage. On the left, an identity clerk stamps a card, in the center a ledger updates in real time, and on the right a video call console shares space with a code editor. The composition explains that the product is not one monolith, but a tightly managed relay across identity, state, and media.
TalentScope works because each backend owns one job, and the seams are deliberate.
Key Takeaways

The app's real trick: it never asks one system to do everything

TalentScope is easiest to understand as a choreography layer. Clerk handles who you are, Convex remembers what the product knows, and Stream moves the live call. That separation is the point. The app feels unified because each boundary is narrow and explicit.

The core loop is identity to sync to room to review, with Convex acting as the shared memory in the middle.

Clerk signs the user in. Convex remembers who they are.

This is the cleanest technical pattern in the repo. Clerk owns authentication, but the app does not let identity live only inside the auth layer. A webhook hands the user event to Convex, where it becomes a real record with role, email, and other app-specific fields.

// convex/http.ts
export const http = httpRouter();

http.route({
  path: "/clerk-webhook",
  method: "POST",
  handler: httpAction(async (ctx, req) => {
    const event = await verifyClerkWebhook(req);

    if (event.type === "user.created" || event.type === "user.updated") {
      await ctx.runMutation(api.users.syncUser, {
        clerkId: event.data.id,
        email: event.data.email_addresses[0]?.email_address,
        name: event.data.first_name,
      });
    }

    return new Response("ok");
  }),
});
A close-up scene shows an envelope stamped with a Clerk event landing in a Convex inbox. Inside the inbox, a user record appears with a role badge and an index line, making the data handoff feel physical. The image explains how <span class=the webhook sync pattern solves the two-sources-of-truth problem by turning auth events into app data." data-prompt="Create an editorial illustration rendered entirely in black ink on a pure white background. The image depicts a stamped Clerk event envelope landing in a Convex inbox tray. Inside the tray, a user record card emerges with a role badge, a clerk id line, and a small index tab, while a thin line connects the envelope to the record like a data conduit. The artist uses tightly packed crosshatching lines layered at different angles to build up shadow and form, with clean open areas of white for highlights. The line work has the quality of a classic metal engraving, precise and deliberate, with varied line weights where bold contour lines define shapes and finer interior lines create tonal depth. The overall style evokes vintage newspaper editorial illustrations from The Economist or Wall Street Journal. No color, no gradients, no grey fills. Only black lines on white. The background MUST be pure white #FFFFFF. No paper texture, no cream, no off-white, no noise, no grain. Perfectly clean flat white background." loading="lazy">
The webhook is not just plumbing. It is the moment identity becomes queryable product state.

That choice does more than keep the database tidy. It means roles, scheduling, and interview history can all hang off the same durable record. You are not translating between an auth provider and an app database every time the UI needs a decision.

Inside the interview room

The candidate flow is built to feel calm. `MeetingSetup.tsx` handles camera and microphone checks, then the view switches into `MeetingRoom.tsx` inside a `StreamCall` provider. That wrapper matters because the call context, the live controls, and the editor all need the same session state before the interview starts.

<StreamCall call={call}>
  {joined ? (
    <MeetingRoom interviewId={id} />
  ) : (
    <MeetingSetup onJoin={() => setJoined(true)} />
  )}
</StreamCall>
A medium editorial scene shows a split interview room with a camera preview and audio controls on one side and a Monaco-style code editor on the other. The divider between them is weighted and draggable, so the workspace feels like a deliberate instrument rather than a generic video call. The image explains how the product balances conversation and coding inside one interface.
The room is not just a meeting window. It is a controlled workspace for talking and typing at the same time.

The clever part is that the room does not try to be a full collaboration suite. It is opinionated. The editor is there to support the interview, not to become the product itself.

Why the dashboard feels instant

The interviewer experience becomes compelling only when the backend loop closes. Convex queries keep the dashboard live, so a status change or a new comment appears without a refresh. Pass, fail, and notes all ride the same data path, which makes the whole control surface feel immediate.

A wide control desk shows a grid of interview cards, outcome toggles, and note fields arranged like an operations panel. Small indicators shift as if the data has just updated, which makes the interface feel reactive rather than static. The image explains why Convex-powered queries turn the dashboard into a live feedback loop.
The dashboard works because state changes ripple through it as soon as they land in Convex.

That is what makes the product feel alive. The interviewer is not managing a pile of disconnected tools. They are working inside one feedback loop where outcome, comment, and record update each other.

What this replaces, and what it does not

ApproachWhat you assembleWhat it buys you
Generic meeting stackVideo call, shared doc, separate notes, manual follow-upFlexibility, but lots of context switching and weak state cohesion
Custom buildWebRTC, auth, token generation, realtime storage, role logicMaximum control, but you own the plumbing and the edge cases
TalentScopeClerk, Convex, Stream, Monaco, and a workflow on topOpinionated interview flow with far less integration overhead
A split composition shows a messy pile of separate tools on one side and a single integrated interview console on the other. The left side feels scattered, while the right side compresses the workflow into one focused station. The image reinforces the tradeoff between stitching tools together and adopting one opinionated system.
TalentScope trades breadth for coherence. It is narrower than a generic collaboration stack, but much easier to run as an interview workflow.

That comparison is the real verdict. TalentScope is not trying to be Zoom, and it is not trying to be a general collaboration platform. It is an opinionated interview system, and that is why the architecture stays so legible.

The tradeoffs hidden behind the elegance

The codebase is clean, but it is still young. The strongest pattern here is the separation of identity, state, and media, yet that same discipline exposes a limitation: the editor looks more local-first than truly collaborative. For a live coding interview, that is acceptable. For pair programming at scale, it would need a shared-state layer.

There is also a broader tradeoff in the stack itself. By leaning on managed services, the project avoids a custom server and a lot of operational complexity, but it also depends on vendor boundaries staying stable. That is a smart bet for an interview product, not a universal one.