Rex Automaton
All posts
CRM & Pipeline AutomationAugust 4, 20269 min read

Fathom to Salesforce: Auto-Create Tasks and Notes From Calls

We built a Fathom-to-Salesforce integration that turns AI call summaries into Tasks, Notes, and follow-ups automatically. Uses Fathom webhooks/API and respects Salesforce object limits.

By Jacky Lei

We built and shipped a Fathom to Salesforce automation that listens for completed calls and AI action items, then creates Salesforce Tasks, Notes, and next steps automatically. It is for sales and success teams that want every follow-up captured without manual CRM logging. This guide shows how it works, how to build it, and the gotchas we hit in production.

Definition: Fathom to Salesforce automation is a webhook and API pipeline that turns meeting summaries and action items into CRM records on Contacts, Accounts, or Opportunities without manual copy paste.

The problem it solves

Answer first: the integration removes the copy paste step after every call and guarantees a Task exists for every action item with the right owner and due date.

Teams told us the post-call workflow was brittle: someone downloads a summary, writes a few bullets into Salesforce, promises a follow-up, then forgets to create a Task. Two days later the thread is cold. Fathom already has the summary and action items, but native CRM syncs are constrained by object coverage and mapping. We bridged that gap so every call ends with structured Tasks and searchable Notes in Salesforce.

Manual processAutomated with Fathom to Salesforce
Skim Fathom recap and rewrite in CRMFathom webhook triggers, API pulls details, Task and Note are created
Decide who owns each follow-upOwner routed by attendee and calendar rules
Pick a due date from memoryDates derived from phrase parsing and meeting time windows
Paste bullets into a long NoteAI summary preserved, truncated safely to Salesforce limits
Forget to create a TaskIdempotent guard prevents duplicates and enforces one Task per action item

How the automation works

Answer first: Fathom sends a webhook on meeting completion. Our service fetches details via the Fathom API, resolves the attendee email to a Salesforce Contact and related Account or open Opportunity, then creates a Task and a Note. Rate limits and field limits are respected.

  • Fathom webhooks and API: Fathom exposes a public REST API at https://api.fathom.ai/external/v1 with X-Api-Key auth and webhooks you can create and list. We receive the event, then fetch meeting details. Fathom's rate limit is 60 calls per minute per user across keys.
  • Matching and routing: We match attendee primary emails to Salesforce Contacts. When an open Opportunity exists with an OpportunityContactRole link, we relate the Task to that deal. Else we attach to the Contact or Account.
  • Task creation: One Task per action item: Subject, Due Date, Priority, Status, and the WhoId or WhatId get set. We attach the full context in a Note so the Task stays readable.
  • Idempotency: We hash per-action-item content and store it so replays or edits do not create duplicates.
  • Zapier or Make fallback: Zapier has instant triggers for new recordings and summaries but no actions. Make includes Watch New Recordings and Get Summary. We use webhooks for production and keep a Make scenario as a backup watcher.

Fathom to Salesforce workflow: Fathom webhooks feed an AI processing service that maps attendees to Salesforce, then writes Tasks and Notes with idempotency and field-limit handling

Step-by-step: how to build it

1) Register a Fathom webhook

Answer first: create a webhook in Fathom that points to your secure endpoint. Authenticate with X-Api-Key.

# Create a webhook (see Fathom docs for exact body fields and event types)
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $FATHOM_API_KEY" \
  https://api.fathom.ai/external/v1/webhooks \
  -d '{
    "target_url": "https://yourdomain.com/webhooks/fathom",
    "description": "Meeting completed handler"
  }'

Gotcha: do not assume payload shape. We store the raw body, verify the signature if configured, then call the API to pull authoritative meeting details.

2) Receive the webhook and fetch meeting details

Answer first: handle the POST, then retrieve the meeting and summary via Fathom's API before writing anything to Salesforce.

// server/webhooks.js
import express from "express";
import fetch from "node-fetch";
 
const router = express.Router();
 
router.post("/webhooks/fathom", async (req, res) => {
  // Acknowledge quickly
  res.status(202).end();
 
  try {
    const event = req.body; // store raw for audit
    const meetingId = event?.meeting_id || event?.id; // do not rely on one field
 
    // Fetch latest meetings then find the one we care about
    const resp = await fetch("https://api.fathom.ai/external/v1/meetings", {
      headers: { "X-Api-Key": process.env.FATHOM_API_KEY }
    });
    const data = await resp.json();
    const meeting = Array.isArray(data?.results)
      ? data.results.find(m => m.id === meetingId)
      : null;
 
    if (!meeting) return;
 
    // Extract safely: attendees, summary text, action items
    const attendees = meeting.attendees || [];
    const summary = meeting.summary || "";
    const actions = meeting.action_items || [];
 
    // Hand off to the Salesforce writer
    await queueForSalesforce({ meetingId, attendees, summary, actions });
  } catch (e) {
    console.error("fathom webhook error", e);
  }
});
 
export default router;

Gotcha: Fathom rate limit is 60 calls per minute per user. Batch reads and cache per meeting ID to avoid refetch storms under retries.

3) Resolve attendees to Salesforce records

Answer first: match the primary attendee email to a Contact, then prefer an open Opportunity with an OpportunityContactRole.

// lib/salesforce.js
import fetch from "node-fetch";
 
export async function findSfContext(email, sf) {
  // 1: contact by email
  const q1 = `SELECT Id, AccountId FROM Contact WHERE Email = '${email.replace(/'/g, "\'")}' LIMIT 1`;
  const c = await sf.query(q1);
  if (!c.records?.length) return {};
  const contactId = c.records[0].Id;
  const accountId = c.records[0].AccountId;
 
  // 2: open opp linked to this contact
  const q2 = `SELECT Opportunity.Id FROM OpportunityContactRole WHERE ContactId='${contactId}' AND Opportunity.IsClosed=false LIMIT 1`;
  const o = await sf.query(q2);
  const oppId = o.records?.[0]?.Opportunity?.Id;
 
  return { contactId, accountId, oppId };
}

Gotcha: Fathom's native Salesforce sync does not write to Leads. Our custom integration can create Tasks under Leads by resolving Lead email to WhoId when Contact is missing.

4) Create Salesforce Tasks for each action item

Answer first: one Task per action item with an idempotency key to prevent duplicates.

// lib/write-task.js
import crypto from "crypto";
 
export async function createTaskForAction({ action, context, sf, ownerId }) {
  const key = crypto.createHash("sha1").update(action.text).digest("hex");
 
  const dupCheck = await sf.query(
    `SELECT Id FROM Task WHERE Description LIKE '%${key}%' LIMIT 1`
  );
  if (dupCheck.records?.length) return;
 
  const body = {
    Subject: `Call follow-up: ${action.title || action.text.slice(0, 60)}`,
    Status: "Not Started",
    Priority: "Normal",
    ActivityDate: action.dueDate || new Date().toISOString().slice(0, 10),
    OwnerId: ownerId,
    WhoId: context.contactId, // can be a Lead Id as well
    WhatId: context.oppId || context.accountId || null,
    Description: `${action.text}\n\nRef: ${key}`
  };
 
  await sf.post("/services/data/v59.0/sobjects/Task", body);
}

Gotcha: store a short hash in Description or a custom field so replays do not duplicate Tasks.

5) Attach the full AI summary as a Note

Answer first: write a Note related to the same record and truncate to Salesforce field limits.

export async function createNote({ summary, context, sf }) {
  const max = 32000; // soft guard below Salesforce long-text limits
  const body = {
    Title: "Fathom call summary",
    Body: summary.slice(0, max),
    ParentId: context.oppId || context.contactId || context.accountId
  };
  await sf.post("/services/data/v59.0/sobjects/Note", body);
}

Gotcha: Fathom supports one click bulk export of all transcripts via TranscriptExporter. For this integration, keep the Note short and store the canonical transcript URL in a custom field or Description if you need deep context.

6) Handle owners, calendars, and edge cases

Answer first: derive OwnerId from the host calendar, fall back to round robin, and skip internal only events.

function resolveOwnerId({ meeting, fallbacks }) {
  const hostEmail = meeting?.host_email;
  return fallbacks.map[hostEmail] || fallbacks.roundRobin()
}

Gotcha: Internal only meetings and events from secondary calendars may not sync. We skip internal only by default and allow an allowlist per team.

Where it gets complicated

  • No Leads in native sync: Fathom's Salesforce integration writes to Contact, Account, and open Opportunity. Lead objects are not supported. Our pipeline resolves Leads by email and writes Tasks against the Lead WhoId when a Contact is missing.
  • Matching dependency: Deal level writes require an explicit OpportunityContactRole link to an open Opportunity. If that link does not exist, your Task should still land on the Contact or Account so the follow-up is not lost.
  • Field mapping and length: Long text is auto truncated by Salesforce limits and some native mappings only consider certain heading levels. We trim body text proactively and push the full text to a Note instead of overloading Task.Description.
  • Scope and timing: Historical backfill is manual per call. Disconnect or reconnect does not re-sync history. Use Fathom webhooks for forward events and design a one time backfill job if you need history.
  • Rate limits and batching: Fathom is 60 calls per minute per user. Batch calls and cache meetings by ID when retries hit. Salesforce has its own API limits per org and per user, so compact your writes.

What this actually changes

In production this eliminated the post-call logging step and made every action item visible in the pipeline the same day. Reps stopped retyping summaries. Managers saw Tasks attached to the right Opportunities without chasing notes.

One useful benchmark: McKinsey reported sellers spend less than one third of their time on selling activities, with the rest on admin and other tasks. Shifting post-call admin into an automated flow structurally returns time to selling. Source: https://www.mckinsey.com/capabilities/growth-marketing-and-sales/our-insights/revving-up-the-sales-engine-in-your-organization

Frequently asked questions

Does Fathom have an API and webhooks for this?

Yes. Fathom exposes a public API at https://api.fathom.ai/external/v1 with X-Api-Key auth and supports creating and listing webhooks. The published rate limit is 60 calls per minute per user across keys.

Can this write Tasks on Salesforce Leads as well as Contacts?

Fathom's native Salesforce sync does not support Leads. Our custom build uses the Salesforce API, so we can resolve a Lead by email and create Tasks against that Lead's WhoId when no Contact match exists.

Do I need Zapier or Make for this integration?

No. We run it as a small service using Fathom webhooks and API. Zapier has instant triggers but no actions for Fathom. Make includes Watch New Recordings and Get Summary. We keep a Make scenario as a fallback watcher if webhooks are paused.

How do you prevent duplicate Tasks from the same action item?

We compute a stable hash of each action item and store it in the Task Description or a custom field. Before writing we query for that hash. This makes the write idempotent even under webhook retries or manual replays.

Will every meeting sync to Salesforce?

We scope to external calls where an attendee email matches a Salesforce record. Internal only meetings and secondary calendars are excluded by default. Backfill is not automatic: if you need history, run a one time backfill script.

What happens if there is no open Opportunity?

We attach the Task and Note to the Contact. If there is an Account, we relate the Note there as well. When a new Opportunity opens later, future calls will attach at the deal level once an OpportunityContactRole is present.

If you want every call to end with a Salesforce Task and a readable Note without anyone retyping, this pattern is proven. We have also shipped related Fathom workflows like automate-fathom-meeting-notes. See our CRM automation services or book a 15 minute call to scope your exact mapping and routing.

Want us to build this for you?

15-minute discovery call. No pitch. We tell you what to automate first.

Book a Discovery Call

Related reading