REJCOB
← All work

Call intelligence pipeline

From a telephony webhook to a searchable record: transcription, schema-validated extraction and full-text search over every conversation.

Role
Architecture & design
Period
2026
Company
Personal project

Async

the webhook is acknowledged before any processing

Schema

validated LLM output — typed fields, not free text

Staged

each step re-runnable without repeating the others

#Context

Teams that work by phone — support, sales, clinics, real-estate — generate hours of audio that nobody listens to again. A plain transcript is barely better: it is long and unsearchable by meaning. What is useful is a short structured record per call that can be filtered and searched. This design is a provider-hosted-telephony pipeline that produces exactly that, using synthetic recordings as test data.

#Problem & constraints

  • Transcription takes longer than a webhook timeout allows, so the work cannot happen in the request.
  • Free-text LLM summaries cannot be filtered, counted or trusted to have the same shape twice.
  • Transcription is the expensive step; improving the extraction prompt should not mean paying for it again.

#Architecture

Twiliorecording callbackBullMQack firstWhispertimestamped segmentsLLM extractionJSON schemaPostgreSQLstages + full-textCall explorerReact · filter & search
A recording callback fans out into three persisted stages: transcribe, extract, index.

When a call recording is ready, Twilio sends a status callback; the endpoint verifies the signature, stores the event and enqueues a job in BullMQ. The pipeline runs as three independent stages, each persisting its output in PostgreSQL: (1) fetch the recording and transcribe it with Whisper, keeping timestamps per segment; (2) send the transcript to an LLM with a JSON schema — topic, customer intent, objections raised, commitments made, next step, sentiment — and reject any output that fails validation; (3) index the transcript with full-text search. Because each stage stores its result, changing the extraction schema means re-running stage 2 over stored transcripts without touching audio. A React view lists calls with their structured fields, filters by any of them, and jumps from a search hit to the matching moment in the transcript.

#Key decisions & tradeoffs

01Schema-validated extraction over summaries

Decision
Ask the LLM for a typed JSON object and validate it, retrying on failure, instead of storing a prose summary.
Alternatives
A paragraph summary per call, or keyword rules over the transcript.
Why
Typed fields can be filtered, aggregated and checked; a paragraph can only be read. Validation turns a malformed answer into a retry instead of bad data.
Cost of being wrong
The schema has to be designed up front and versioned; changing it means reprocessing old calls.

02Independent, persisted stages

Decision
Split transcription, extraction and indexing into separate jobs that each save their output.
Alternatives
One job that does everything from audio to final record.
Why
A failure in a late stage does not repeat the costly early one, and prompts can be iterated cheaply against stored transcripts.
Cost of being wrong
More moving parts and intermediate state to track per call.

#Results

6 fields

per call: topic, intent, objections, commitments, next step, sentiment

Search

full-text over transcripts, linked to the timestamp

Re-run

extraction over stored transcripts without re-transcribing

#What I'd do differently

The weak point is speaker separation: without it, 'who committed to what' is a guess by the LLM. The next step is dual-channel recordings so each side is transcribed separately, then measuring word error rate and extraction accuracy on a labelled set of synthetic calls.

#Stack

TwilioWhisperOpenAIBullMQPostgreSQLNode.jsTypeScriptReact