wanis.dev
← All work
Recruitment SaaS

CamCha

An AI recruitment platform with AI video interviews, CV parsing, and candidate–job matching, sold on a subscription.

Where
Trybe
Stack
Laravel · Quasar · FastAPI · RabbitMQ

Client work. Diagrams are simplified and contain no client data.

Context

Recruiters wanted to screen candidates faster: parse CVs, match candidates to jobs, generate interview questions, and run first-round interviews with an AI video avatar, with transcripts of what candidates said. All of that depends on slow external AI services that can’t sit inside a web request.

My role

I was part of the team that architected and built it from scratch, from the first weeks of the project. I built most of the Laravel API (models, queued jobs, migrations, the Filament admin, deployment config) and most of the Quasar web app (pages, components, i18n, routing), and contributed to the AI service’s endpoints and its RabbitMQ integration.

Architecture

RabbitMQstart & pollstart & pollwebhook

Quasar web app

Laravel API

Filament admin

MySQL

Horizon queues
Redis

AI service
FastAPI + OpenAI
CV parsing · matching · questions

Video-avatar provider

Transcription provider

Stripe

Fig. 1 — CamCha, simplified
  1. The Quasar web app and the Filament admin talk only to the Laravel API.
  2. AI work (CV parsing, matching, question generation, embeddings) runs in a separate Python service. Laravel sends it work over RabbitMQ and gets results back the same way.
  3. Avatar videos and transcriptions are started by queued jobs that then poll the provider until the result is ready, rescheduling themselves with back-off.
  4. Subscriptions go through Stripe, which reports back by webhook.

Design decisions & trade-offs

  • AI in its own service, over a queue. Laravel stays free of Python and LLM concerns, and a slow AI call never blocks a web request. Trade-off: every result arrives asynchronously, so the app needs status tracking and commands to retry stuck work.
  • Polling jobs instead of provider webhooks. A job checks the avatar video’s status every 30 seconds and reschedules itself, with limited retries and increasing back-off. Trade-off: some wasted calls and a little latency, in exchange for not depending on the provider reaching us.

Stack

Laravel · Vue.js · Quasar · Filament · MySQL · Redis · Laravel Horizon · RabbitMQ · Python · FastAPI · OpenAI · Stripe · Laravel Forge