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 shape | What 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
| Secret | Used for |
|---|---|
FATHOM_API_KEY | REST polling and transcript fetch |
FATHOM_WEBHOOK_SECRET | Webhook signature verification (currently set in Supabase) |
ANTHROPIC_API_KEY | Decision/promise/task extraction |
SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY | Writes |
Cron cadence
| Task | Schedule | Params |
|---|---|---|
sable.fathom.reconcile | Every 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
| Table | What's written |
|---|---|
sources | type='meeting', external_id, full transcript text (body), Fathom metadata |
decisions, promises, tasks | Extracted per meeting, joined back to the source |
approvals | Pending Asana task-create approvals derived from extracted tasks |
knowledge_chunks | Transcript embeddings — including via the link-repair backfill |
activity_events | Ingest activity trail (soft-fail emission) |
connector_checkpoints | Health, high-water-mark, link_repair_attempts metadata |
audit | Webhook 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
backfillKnowledgefixes — either wait for the automatic 5-item sweep at the end of the next scheduled poll, or run it directly withsourceIds/externalIdstargeting that meeting. - A meeting stopped repairing after several relink attempts. Per-source link-repair retries cap
at 3, tracked in
metadata.link_repair_attemptson 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: truereturns thewhsec_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
relatedByIdon 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 receiversupabase/functions/fathom-sync/index.ts— poller + backfill/maintenance operationssupabase/functions/fathom-sync/ingest.ts— sharedingestMeeting/backfillLinkedKnowledgelogicweb/api/meeting.js— Meeting Brief API (readssources/decisions/promises/tasks, generatesmeeting_record)trigger/connectors.ts—sable.fathom.reconcilescheduledocs/bgm-sow-meeting-truth.md,scripts/bgm-sow/— master-register backfill context