Skip to content

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.

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.

  1. Go to Settings → Twilio
  2. Find the Sync Call History section
  3. Each imported phone number has its own Sync History button, or use Sync All Numbers to bulk sync
  4. Optionally set a “since” date to limit the range
POST /api/twilio/sync-call-history
Input: phone_number_id (UUID), since (optional date), recording_batch_size (optional)
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 transcription

Running 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.

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.

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:

  1. Query Twilio for recordings: GET /Accounts/{sid}/Calls/{call_sid}/Recordings.json
  2. If recordings exist, download the first MP3
  3. Save MP3 to storage with org-scoped key: {call_sid}.mp3
  4. Send audio to OpenAI Whisper for diarized transcription
  5. Format transcript with speaker labels
  6. Update call row with recording_url and transcription

If a call has no recording, no background work is queued.

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:

PhaseDescription
FetchingQuerying Twilio Calls API
DeduplicatingChecking for already-imported SIDs
ResolvingContactsCreating or matching contacts
InsertingBatch inserting new call records
UpdatingTimestampsUpdating contact last_contacted_at
ProcessingRecordingsDownloading and transcribing recordings (includes completed/total counters)
DoneSync 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.

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.

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
);
MethodPathDescription
POST/api/twilio/sync-call-historyImport past calls from Twilio
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