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

Fathom Salesforce integration: webhooks to Tasks and Notes

Fathom Salesforce integration with webhooks. Turn meetings into Salesforce Tasks and Notes, update health scores, and trigger objection tasks safely.

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.

Fathom Salesforce integration: native vs webhook build

Answer first: use Fathom webhooks when you need Leads, custom fields, health score updates, or objection workflows. Use the native sync when Contact or Account notes are enough and mapping constraints are acceptable.

  • Native Fathom to Salesforce: writes on Contact, Account, and open Opportunity. Lead objects are out of scope. Field mapping is constrained. It is quick to turn on but limited for routing and custom updates.
  • Webhook plus API build: supports Leads, custom Task logic, Account level Notes, and related Opportunity targeting when an OpportunityContactRole exists. Idempotency and truncation guards keep data clean under retries and limits.

If you also run HubSpot or Pipedrive in parts of the org, we have shipped the same pattern there: see how we associate Fathom calls to HubSpot deals and how we create tasks from Fathom in Pipedrive. For SOW and long-form deliverables, we also generate Google Docs from Fathom notes.

How to set up Fathom webhooks correctly

Answer first: register a webhook, acknowledge fast, fetch authoritative details via API, and make writes idempotent.

  • Choose scope: point the webhook at a dedicated HTTPS endpoint. Keep the event filter scoped to completed meetings if configurable.
  • Acknowledge first: return a 2xx quickly, then process asynchronously so Fathom does not retry on timeouts.
  • Fetch details: do not trust the webhook body alone. Call the Fathom API to pull the meeting, attendees, summary, and action items.
  • Verify authenticity: store the raw body and verify the signature if your webhook is configured with one. Reject mismatches.
  • Idempotency: compute a stable hash per action item and query before write. This prevents dupes during retries or replays.
  • Test safely: test with a staging target using a tunnel or webhook capture tool, then rotate to production keys.

For teams that also open tickets from calls, see our ClickUp pattern: create ClickUp tasks from Fathom calls.

Fathom webhooks: event scope and test plan

Answer first: listen for completed meeting events, confirm your service can replay safely, and prove the full path from webhook to Salesforce records in a sandbox.

  • Event scope: subscribe to completion style events so summaries and action items are available when your handler runs.
  • Replay safety: design the pipeline to accept the same event more than once without creating duplicate Tasks or Notes.
  • Sandbox first: point to a Salesforce sandbox. Validate Contact, Account, and Opportunity associations on test records.
  • Failure alerts: add simple logging and a dead letter queue or spreadsheet log so missed writes are visible to ops.

Tip: if your team uses a lead management playbook, wire this pipeline to your lead follow-up engine. We often pair it with our pattern to automate CRM lead follow-up so every action item turns into a dated task with an owner.

Fathom Salesforce data sync: what lands where

Answer first: Contacts and Accounts always receive context, Opportunities receive context when a contact role exists, and Leads are supported in the webhook build by resolving email.

  • Contacts: every matched attendee email gets Tasks written to the Contact when no deal context exists.
  • Accounts: we attach a concise Note at the Account level when the Contact belongs to an Account.
  • Opportunities: if there is an open Opportunity with an OpportunityContactRole for the Contact, we relate Tasks and Notes to that deal.
  • Leads: native Fathom sync does not cover Leads. Our webhook pipeline can create Tasks related to a Lead by email match.

This is a controlled data sync: we only write what improves follow-up and searchability. If you need long-form outputs for onboarding or proposals, see how we turn Fathom notes into SOW docs.

Customer success: sync transcripts to Accounts and update health scores

Answer first: attach a Note with the call summary to the Account, then adjust a Health Score field based on keywords or action patterns. Open Tasks when objections or risks appear in the summary.

Implementation pattern we ship in production:

  • Account Note: if a Contact maps to an Account, write a concise Note titled Fathom call summary. Trim long text to stay below Salesforce limits and include the transcript URL or reference.
  • Health Score update: define a light rules engine that increments or decrements an Account Health Score. Inputs: presence of action items, next meeting booked, renewal language, or risk keywords in the summary. Write the net score to your custom Health Score field.
  • Objection Tasks: scan the action items or summary for common objections you track. Create one Task per objection with clear Subjects, owners, and due dates. Use the same idempotency keying so an objection does not spawn duplicates.

Example rules stub:

export async function updateHealthAndObjections({ summary, context, sf }) {
  const hasNextMeeting = /next meeting|follow up call/i.test(summary);
  const risk = /(budget|timing|competitor|no decision)/i.test(summary);
  const objections = [
    { key: "budget", rx: /budget/i },
    { key: "timing", rx: /timing|timeline/i },
    { key: "competitor", rx: /competitor|alternative/i }
  ];
 
  # Score movement
  let delta = 0;
  if (hasNextMeeting) delta += 1;
  if (risk) delta -= 1;
 
  # Write your custom health score field
  await sf.patch(`/services/data/v59.0/sobjects/Account/${context.accountId}`, {
    YOUR_HEALTH_SCORE_FIELD: sf.expr("existing + delta") # read-modify-write in your code
  });
 
  # Create objection tasks
  for (const o of objections) {
    if (o.rx.test(summary)) {
      await createTaskForAction({
        action: { text: `Handle ${o.key} objection`, title: `Objection: ${o.key}` },
        context,
        sf,
        ownerId: context.ownerId
      });
    }
  }
}

If you want to push objection handling into CX tooling as well, we also built a flow that creates HubSpot tickets from Fathom calls using the same webhook and mapping rules.

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.

Is this a Fathom Salesforce data sync or a webhook pipeline?

It is a webhook driven data sync. We subscribe to Fathom events, fetch authoritative details, then write Tasks and Notes to the right Salesforce objects with routing and idempotency guards.

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 and create HubSpot tasks from Fathom calls. 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?

Nine questions, about 90 seconds. You see the hours it is costing you, then pick a time. No pitch.

Get your free assessment

Related reading