Skip to main content

Fathom

Fathom is the canonical meeting-truth source for SABLE. Its signed webhook is the primary ingest path; fathom-sync is the 4-hourly reconciliation backstop, plus a set of maintenance operations for backfilling and repairing meetings.

How it works

Webhook (fathom-webhook)

Fathom calls this function directly. It verifies the signed webhook against FATHOM_WEBHOOK_SECRET (edge secret, or a Vault row as fallback), replay-guards on audit.detail->>webhook_id, normalizes the payload, fetches the transcript by recording_id if the payload didn't include it, and calls the shared ingestMeeting. It inserts an audit row and emits activity_events — the activity emission is soft-fail, so a problem writing the activity trail never blocks the ingest itself.

The signature payload is <webhook-id>.<webhook-timestamp>.<raw-body>; the function rejects stale timestamps and replayed webhook ids.

Poller (fathom-sync)

fathom-sync covers two jobs: the scheduled reconciliation sweep, and a set of on-demand maintenance operations, selected by which fields are present in the request body.

Normal reconciliation run (no special flags): list meetings (limit 1–200, default 3; createdAfter or lookbackDays 1–30) → normalize → ingestMeeting per meeting inside a wall-clock time budget (10s–140s, default 110s) → emit activity_events (soft-fail) → run a bounded link-repair sweep (5 items) if budget remains.

Backfill/maintenance operations:

Body shapeWhat it does
{ backfillStubs: true, limit }Backfills stub/record rows
{ backfillRecords: true, limit }Regenerates meeting-record super-summaries
{ backfillKnowledge: true, limit, externalIds?, sourceIds?, dryRun? }The link-repair operation — see below
{ recordingIds: [...], relatedById?, titles? }Targeted backfill by specific Fathom recording ids
{ registerWebhook: true }One-shot webhook registration; returns the whsec_ secret once — store it immediately

backfillKnowledge repairs meetings whose project_id was set after ingest (for example, an operator SQL relink). It indexes the already-stored transcript into knowledge_chunks, appends the living project-note entry from the decisions/promises/tasks already extracted, and inherits the project onto derived rows still missing one. It does not re-run Claude extraction — only the embed step. A small bounded sweep of the same repair (limit 5) also runs automatically at the end of every scheduled poll, so future post-ingest relinks heal without operator action; per-source retries cap at 3 (tracked in metadata.link_repair_attempts — clear it to re-arm a source that hit the cap).

recordingIds targets a specific set of Fathom recording ids — used for master-register backfills. When multiple Fathom bots recorded one call, pass every alternate id under relatedById so ingestion skips a recording if any family member already exists. The primary-id policy is lowest numeric recording id (preferPrimaryRecordingId).

Reported health honesty

The checkpoint written after each run claims a high-water-mark/coverage window only over what was actually processed — never the full requested listing — specifically because an earlier version could report success while having only partially processed a batch. stopped_early is reported whenever the time budget cuts a run off before it finished its work.

Secrets

SecretUsed for
FATHOM_API_KEYREST polling and transcript fetch
FATHOM_WEBHOOK_SECRETWebhook signature verification (currently set in Supabase)
ANTHROPIC_API_KEYDecision/promise/task extraction
SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEYWrites

Cron cadence

TaskScheduleParams
sable.fathom.reconcileEvery 4 hours, :15 (15 */4 * * *){limit:25, lookbackDays:3}, loops up to 8 passes while stopped_early

The webhook has no schedule — it fires on Fathom's own delivery timing and is the primary path; the poller is reconciliation, not the main ingest route.

Table mapping

TableWhat's written
sourcestype='meeting', external_id, full transcript text (body), Fathom metadata
decisions, promises, tasksExtracted per meeting, joined back to the source
approvalsPending Asana task-create approvals derived from extracted tasks
knowledge_chunksTranscript embeddings — including via the link-repair backfill
activity_eventsIngest activity trail (soft-fail emission)
connector_checkpointsHealth, high-water-mark, link_repair_attempts metadata
auditWebhook replay-guard + one row per webhook delivery

Duplicate behavior

Historical duplicate sources.external_id rows exist in the data, so ingestion uses a lookup index and skips when it finds an existing source, rather than enforcing a hard uniqueness constraint across all historical data.

What can go wrong

  • A meeting's project link was fixed manually, but the derived decisions/tasks still show the old project (or none). This is exactly what backfillKnowledge fixes — either wait for the automatic 5-item sweep at the end of the next scheduled poll, or run it directly with sourceIds/externalIds targeting that meeting.
  • A meeting stopped repairing after several relink attempts. Per-source link-repair retries cap at 3, tracked in metadata.link_repair_attempts on the source. Clear that field to re-arm it.
  • The link-repair scan itself failed with a URL-too-large error. This was a real regression — the existence check was putting a whole page of UUIDs into a single URL. It was fixed (see #218, deployed 2026-09-09); if it recurs, check whether the batch size for the existence check grew again.
  • A late-registered webhook secret was lost. registerWebhook: true returns the whsec_ value exactly once. If it wasn't saved, the webhook must be re-registered to get a new one — Fathom does not resurface a previously issued secret.
  • A recorded meeting appears twice because two Fathom bots joined the same call. Pass every alternate recording id under relatedById on the next targeted backfill so the ingestion can recognize the family and skip re-ingesting it under a second id.

Where the code lives

  • supabase/functions/fathom-webhook/index.ts — signed webhook receiver
  • supabase/functions/fathom-sync/index.ts — poller + backfill/maintenance operations
  • supabase/functions/fathom-sync/ingest.ts — shared ingestMeeting / backfillLinkedKnowledge logic
  • web/api/meeting.js — Meeting Brief API (reads sources/decisions/promises/tasks, generates meeting_record)
  • trigger/connectors.tssable.fathom.reconcile schedule
  • docs/bgm-sow-meeting-truth.md, scripts/bgm-sow/ — master-register backfill context