Call Sync
Loquent syncs call history from BYO Twilio accounts on demand. You trigger the import from Settings, and the system fetches completed calls, downloads recordings, and transcribes audio in the background.
What It Does to Your Twilio Account
Section titled “What It Does to Your Twilio Account”Nothing. The call history sync is entirely read-only. It queries Twilio’s Calls REST API and writes results to Loquent’s database. No changes are made in Twilio.
How to Sync
Section titled “How to Sync”- Go to Settings → Twilio
- Find the Sync Call History section
- Each imported phone number has its own Sync History button, or use Sync All Numbers to bulk sync
- Optionally set a “since” date to limit the range
POST /api/twilio/sync-call-historyInput: phone_number_id (UUID), since (optional date), recording_batch_size (optional)How the Sync Works
Section titled “How the Sync Works”For each direction (inbound + outbound): GET /Accounts/{sid}/Calls.json?To={number}&Status=completed GET /Accounts/{sid}/Calls.json?From={number}&Status=completed
→ Follows next_page_uri for pagination → Filters by StartTime>= if "since" provided → Deduplicates by Twilio Call SID
For each unique contact phone: → Resolves or creates a contact
Batch inserts (500 calls at a time): → INSERT ... ON CONFLICT DO NOTHING (idempotent by call_sid)
Returns: { imported: N, skipped: N }
Background tasks (5 concurrent by default): → Fetch recordings from Twilio API → Download MP3 audio to storage → Transcribe using OpenAI Whisper with diarization → Update call records with recording_url and transcriptionRunning the sync multiple times is safe. Already-imported calls are skipped by SID, and recordings are only processed for new calls.
After inserting new calls, the sync updates last_contacted_at on affected contacts. Each contact’s timestamp is set to their most recent call — but only if the synced timestamp is newer than the existing value.
Contact Resolution
Section titled “Contact Resolution”Each call involves one external phone number (the contact). The sync resolves that phone to an existing contact or creates a stub contact with source=call_sync.
Inbound calls use from as the contact phone. Outbound calls use to.
Recording Processing
Section titled “Recording Processing”After call records are imported, Loquent spawns background tasks to download and transcribe recordings. This happens asynchronously in batches of 5 (configurable).
Flow per recording:
- Query Twilio for recordings:
GET /Accounts/{sid}/Calls/{call_sid}/Recordings.json - If recordings exist, download the first MP3
- Save MP3 to storage with org-scoped key:
{call_sid}.mp3 - Send audio to OpenAI Whisper for diarized transcription
- Format transcript with speaker labels
- Update
callrow withrecording_urlandtranscription
If a call has no recording, no background work is queued.
Real-Time Progress Events
Section titled “Real-Time Progress Events”The sync publishes progress over WebSocket so the UI can show live status. Events are scoped to the organization’s real-time channel.
Event type: twilio.call_sync.progress
Phases:
| Phase | Description |
|---|---|
Fetching | Querying Twilio Calls API |
Deduplicating | Checking for already-imported SIDs |
ResolvingContacts | Creating or matching contacts |
Inserting | Batch inserting new call records |
UpdatingTimestamps | Updating contact last_contacted_at |
ProcessingRecordings | Downloading and transcribing recordings (includes completed/total counters) |
Done | Sync complete |
pub struct CallSyncProgress { pub phone_number_id: Uuid, pub phase: CallSyncPhase, pub fetched: Option<usize>, pub new_count: Option<usize>,}The frontend transitions to “Done” only when the WebSocket Done event arrives — not when the HTTP response returns. This ensures the UI waits for background recording processing to finish.
Parallel Sync All
Section titled “Parallel Sync All”Sync All Numbers fires all per-number sync requests concurrently using join_all(). Each number shows independent real-time progress and results are aggregated after all complete.
Call Data Model
Section titled “Call Data Model”CREATE TABLE call ( id UUID PRIMARY KEY, phone_number_id UUID, -- Loquent phone number call_sid TEXT UNIQUE, -- Twilio Call SID (indexed, prevents duplicates) from_to_number TEXT, -- Contact phone number contact_id UUID, -- Resolved contact (nullable) duration_secs INT, -- Call duration in seconds status TEXT, -- "completed" recording_url TEXT, -- URL to fetch recording (Twilio API path) transcription TEXT, -- Diarized transcript with speaker labels created_at TIMESTAMP);API Reference
Section titled “API Reference”| Method | Path | Description |
|---|---|---|
POST | /api/twilio/sync-call-history | Import past calls from Twilio |
Module Structure
Section titled “Module Structure”src/mods/twilio/├── api/│ └── sync_call_history_api.rs├── services/│ └── sync_call_history_service.rs # Core sync + recording processing + progress events├── types/│ └── sync_call_history_progress_type.rs # CallSyncPhase + CallSyncProgress└── utils/ └── fetch_twilio_call_history_util.rs # Paginated Twilio API fetch
src/mods/contact/services/└── update_contacts_last_contacted_batch_service.rs # Batch timestamp updates
src/mods/settings/components/└── sync_call_history_section_component.rs # Realtime progress UI