BDS0.1

Cal.com Booking Intake

Turns Cal.com bookings into CRM leads and meetings, creating the person in Core when needed.

Reviewed 2026-11-06Markdown
Platform
n8n · Open in n8n
Status
Active
Workflow ID
QtaJdIPuUy8l6AXn
Nodes
32
n8n version
d1323e82-7bd7-4179-b1d9-2c00b8a9f260
Last saved in n8n
2026-11-06T21:25:19.572Z
Systems
CRMCore

Cal.com Booking Intake turns a Cal.com booking made on a seller’s landing page into CRM records. A new booking becomes a Deal (the lead) at Meeting Scheduled, or moves the person’s open Deal with that seller to Meeting Scheduled, plus an Interaction (the meeting) for the right seller, and the person is created in Core when nobody has that e-mail yet. A reschedule moves the same meeting; a cancellation or rejection marks it cancelled and sets the meeting’s next contact to today. One webhook serves every seller’s Cal.com account.

The CRM maintainer owns the seller routing and recovery; the Core maintainer reviews the people it creates.

Flow

Generated from the n8n version in the box above: every box is a node and every arrow is a connection in n8n. Pink pills are triggers, yellow hexagons are If and Switch nodes with their outputs on the arrows, blue cylinders read (Airtable, Data Table, HTTP GET), green cylinders write, purple boxes call another workflow, and dashed arrows are error outputs.

Cancellation and rejection

Reschedule

New booking

Receive and verify

true

false

true

false

true

false

true

false

true

false

true

false

true

false

Cal.com Webhook

Sign Body

Verify & Parse

Is New Booking?

Is Reschedule?

Find Duplicate
GET CRM Interaction

Get Lead Sources
GET CRM Lead Source

Find Person (CRM)
GET CRM People

Resolve

Needs Core?

Find Person (Core)
GET Core People

Exists in Core?

Create Person (Core)
POST Core People

Wait 1 min

Recheck Person (CRM)
GET CRM People

Sync Check

Person Ready?

Find Deals
GET CRM Deal

Prepare Deal

Existing Deal?

Update Deal
PATCH CRM Deal

Create Deal
POST CRM Deal

Prepare Interaction

Create Interaction
POST CRM Interaction

Find Original Meeting
GET CRM Interaction

Prepare Reschedule

Original Found?

Move Meeting
PATCH CRM Interaction

Find Booked Meeting
GET CRM Interaction

Prepare Cancellation

Mark Meeting Cancelled
PATCH CRM Interaction

Mermaid source
flowchart TD
  subgraph g0["Receive and verify"]
    n0(["Cal.com Webhook"]):::trigger
    n26["Sign Body"]:::logic
    n29["Verify & Parse"]:::logic
    n13{{"Is New Booking?"}}:::decision
    n14{{"Is Reschedule?"}}:::decision
  end
  subgraph g1["New booking"]
    n8[("Find Duplicate<br/><small>GET CRM Interaction</small>")]:::read
    n12[("Get Lead Sources<br/><small>GET CRM Lead Source</small>")]:::read
    n11[("Find Person (CRM)<br/><small>GET CRM People</small>")]:::read
    n25["Resolve"]:::logic
    n17{{"Needs Core?"}}:::decision
    n10[("Find Person (Core)<br/><small>GET Core People</small>")]:::read
    n5{{"Exists in Core?"}}:::decision
    n3[("Create Person (Core)<br/><small>POST Core People</small>")]:::write
    n30["Wait 1 min"]:::wait
    n24[("Recheck Person (CRM)<br/><small>GET CRM People</small>")]:::read
    n27["Sync Check"]:::logic
    n19{{"Person Ready?"}}:::decision
    n7[("Find Deals<br/><small>GET CRM Deal</small>")]:::read
    n21["Prepare Deal"]:::logic
    n4{{"Existing Deal?"}}:::decision
    n28[("Update Deal<br/><small>PATCH CRM Deal</small>")]:::write
    n1[("Create Deal<br/><small>POST CRM Deal</small>")]:::write
    n22["Prepare Interaction"]:::logic
    n2[("Create Interaction<br/><small>POST CRM Interaction</small>")]:::write
  end
  subgraph g2["Reschedule"]
    n9[("Find Original Meeting<br/><small>GET CRM Interaction</small>")]:::read
    n23["Prepare Reschedule"]:::logic
    n18{{"Original Found?"}}:::decision
    n16[("Move Meeting<br/><small>PATCH CRM Interaction</small>")]:::write
  end
  subgraph g3["Cancellation and rejection"]
    n6[("Find Booked Meeting<br/><small>GET CRM Interaction</small>")]:::read
    n20["Prepare Cancellation"]:::logic
    n15[("Mark Meeting Cancelled<br/><small>PATCH CRM Interaction</small>")]:::write
  end
  n0 --> n26
  n26 --> n29
  n29 --> n13
  n13 -->|true| n8
  n13 -->|false| n14
  n14 -->|true| n9
  n14 -->|false| n6
  n9 --> n23
  n23 --> n18
  n18 -->|true| n16
  n18 -->|false| n8
  n6 --> n20
  n20 --> n15
  n8 --> n12
  n12 --> n11
  n11 --> n25
  n25 --> n17
  n17 -->|true| n10
  n17 -->|false| n7
  n10 --> n5
  n5 -->|true| n30
  n5 -->|false| n3
  n3 --> n30
  n30 --> n24
  n24 --> n27
  n27 --> n19
  n19 -->|true| n7
  n19 -->|false| n30
  n7 --> n21
  n21 --> n4
  n4 -->|true| n28
  n4 -->|false| n1
  n1 --> n22
  n22 --> n2
  n28 --> n22
  classDef trigger fill:#fde7f3,stroke:#d6409f,color:#111
  classDef decision fill:#fff7e0,stroke:#c99a00,color:#111
  classDef read fill:#e8f1ff,stroke:#3b82f6,color:#111
  classDef write fill:#e7f8ee,stroke:#16a34a,color:#111
  classDef subflow fill:#f3e8ff,stroke:#9333ea,color:#111
  classDef wait fill:#f1f1f1,stroke:#888,color:#111
  classDef logic fill:#ffffff,stroke:#999,color:#111

Systems it touches

System Table Reads Writes
CRM Interaction Find Duplicate, Find Original Meeting, Find Booked Meeting Create Interaction, Move Meeting, Mark Meeting Cancelled
CRM Deal Find Deals Create Deal, Update Deal
CRM People (mirror of Core People) Find Person (CRM), Recheck Person (CRM) Nothing
CRM Automation: Lead Source Get Lead Sources Nothing
Core People Find Person (Core) Create Person (Core)

Every Airtable call is an HTTP Request node with one Airtable token credential; the workflow uses no Airtable node. Reads filter by field name ({Booking UID}, {Email} in the CRM mirror, {Email*} in Core); writes use field IDs. Renaming one of those three fields breaks the lookups without any error: the formula fails and the node returns nothing.

Other systems only register the workflow. Communication lists it as the Webhook channel cal-com-bookings → n8n → CRM. Systems Central has a System row for it. The Login and Tool Identifier it matches on are lookups that reach the CRM from Tooling and Access through the Tool Account mirror.

Trigger and signature

Cal.com Webhook accepts a POST on a fixed path. It uses the default response mode, so Cal.com gets HTTP 200 as soon as the request arrives, before any check or write. A 200 in Cal.com’s webhook log only says that n8n received the call.

Sign Body computes HMAC-SHA256 over JSON.stringify(body) with the Cal.com webhook secret. Verify & Parse compares the result with the x-cal-signature-256 header and throws “Invalid Cal.com signature (x-cal-signature-256)” when they differ or the header is missing. That execution ends in error and writes nothing.

Verify & Parse continues only for BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED and BOOKING_REJECTED. Any other event returns no items and the execution ends there.

What Verify & Parse reads from the payload

Value Taken from, in this order When missing
First and last name responses.name (object with firstName/lastName, or text split on the first space), then the first attendee’s name Full name becomes “Cal.com lead”
Lead e-mail responses.email, then the first attendee’s e-mail; trimmed and lower-cased Every e-mail lookup becomes FALSE()
Phone responses.attendeePhoneNumber, responses.phone, then the attendee’s phone number Not written
Lead notes additionalNotes, responses.notes, then description Not written
Seller account keys Organizer e-mail, username and id, plus externalId and primaryEmail of each destination calendar; lower-cased No seller match
Meeting start startTime The time n8n processed the call
Meeting day and today Calendar date in the organizer’s time zone Time zone America/Sao_Paulo; an invalid zone falls back to the UTC date
Booking UID uid, then bookingId The duplicate and cancellation lookups become FALSE()
Original UID (reschedule) rescheduleUid, fromReschedule, then originalRescheduledBooking.uid The original lookup becomes FALSE()
Reason cancellationReason, then rejectionReason Not appended
Meeting URL metadata.videoCallUrl, videoCallData.url, location when it starts with http, then https://app.cal.com/booking/<uid> Not written
Title title “Cal.com meeting”

New booking

BOOKING_CREATED goes through these steps in order.

  1. Duplicate check. Find Duplicate looks for any Interaction with the same Booking UID. Resolve stops the execution when one exists.
  2. Seller routing. Get Lead Sources reads Automation: Lead Source. Resolve takes the first row that is Active, whose Tool includes cal.com, and whose Tool Identifier or Login equals one of the seller account keys. The row’s Follow up Target: Person becomes the accountable person and Follow up Target: Company the accountable company. When no row matches, the flow continues without a seller.
  3. Person in the CRM. Find Person (CRM) looks for the e-mail in the CRM People mirror. When it is found, the flow goes straight to step 5.
  4. Person in Core. With no CRM person and an e-mail present, Find Person (Core) searches Core People. When the person is not there either, Create Person (Core) adds them with the fields under Fields written. Either way the flow then waits for the CRM mirror: Wait 1 min, Recheck Person (CRM), Sync Check. It leaves the loop when the person appears or after the tenth attempt, about ten minutes. Without an e-mail, steps 3 and 4 find nobody and nothing is created in Core.
  5. Deal. Find Deals reads the Deals linked to the person (their Deals (Counterparty)). Prepare Deal keeps the open ones (Deal Status Open) and reuses the first whose Accountable (Person)* contains the routed person, or, with no seller routed, the first one with no accountable person. Update Deal moves that Deal to Meeting Scheduled when its Deal Stage* is Lead, Contacted or empty; a later stage (Meeting Scheduled, Pitched, Proposal Sent, Negotiation) is kept. Without a match, or without a CRM person, Create Deal adds a new Deal at Meeting Scheduled.
  6. Interaction. Create Interaction adds the meeting, linked to that Deal in Deal° and to the person in People°, with Result Meeting booked. No Relationship is created, and the flow does not set Campaign° on the Deal.

Reschedule

BOOKING_RESCHEDULED runs Find Original Meeting with the original UID. Prepare Reschedule keeps only an Interaction whose Result is Meeting booked.

When it finds one, Move Meeting updates that same Interaction: Date*, Title*, Booking UID (now the new UID), Next Contact Date° and Next Action°, the Evidence URL when the payload has a meeting URL, and a line appended to Note. The Deal does not change, and neither do Result and Accountable.

When it finds none (the original was cancelled or never reached the CRM), the item goes to Find Duplicate; see Known gaps for what happens there.

Cancellation and rejection

BOOKING_CANCELLED and BOOKING_REJECTED run Find Booked Meeting with the current UID. Prepare Cancellation picks the Interaction whose Result is Meeting booked. When there is none (already cancelled, or the booking never reached the CRM) the execution ends without writing.

Mark Meeting Cancelled sets Result to Meeting cancelled, Next Contact Date° to today, Next Action° to “Meeting cancelled, reach out to rebook” (“rejected” for a rejection) and appends a line with the reason to Note. That Interaction is where the follow-up shows. The Deal does not change: its Deal Stage* stays at Meeting Scheduled, or wherever it was.

Fields written

Core People (Create Person (Core))

Field Value
First Name* First name, or the full name when there is no first name
Last Name* Last name, only when present
Email* Lead e-mail
Telephone* Phone, only when present
Status Proposed
Tags cal-lead-n8n
Notes “Created by n8n from a Cal.com booking (uid …) on <date>.” plus the Lead Source name and the Cal.com account

CRM Deal

The workflow writes nothing to Relationship, which was deleted on 2026-11-04.

Field Create Deal Update Deal
Deal Stage* Meeting Scheduled Meeting Scheduled when it was Lead, Contacted or empty; otherwise the current stage
Source° Inbound Not changed
Notes° Lead card, lead notes, and warnings when the person or the seller is missing Not changed
Tags cal-lead-n8n Not changed
People° The CRM person, when found Not changed
Accountable (Person)* Routed person, when found Not changed
Accountable (Company)* Routed company, when found Not changed

Company° is not written; the seller fills it when the lead’s company is known.

CRM Interaction

Field Create Interaction Move Meeting Mark Meeting Cancelled
Deal° The Deal from step 5 Not changed Not changed
People° The CRM person, when found Not changed Not changed
Date* Meeting start New start Not changed
Title* Meeting title New title Not changed
Type Meeting Not changed Not changed
Result Meeting booked Not changed Meeting cancelled
Note Lead card, lead notes, location, Cal.com account, Lead Source name Appends “Rescheduled via Cal.com on <today> from <old start>.” Appends “Meeting cancelled via Cal.com on <today>.” and the reason
Booking UID Booking UID New UID Not changed
Evidence URL Meeting URL, when present New meeting URL, when present Not changed
Next Contact Date° Meeting day New meeting day Today
Next Action° “Meeting booked via Cal.com: <title>” “Meeting rescheduled via Cal.com: <title>” “Meeting cancelled (or rejected), reach out to rebook”
Accountable (Person) Routed person, when found Not changed Not changed
Accountable (Company) Routed company, when found Not changed Not changed
Tags cal-lead-n8n Not changed Not changed

Company° is not written. Writes to Deal, Interaction and Core People send typecast: true, so a select value that does not exist yet (a renamed Result or Deal Stage*, for example) is created as a new option instead of failing.

Nodes

The workflow has 32 nodes: 31 that run and one sticky note. For an If node, output 0 is true and output 1 is false.

Node Type What it does
Cal.com Webhook webhook Receives the POST from Cal.com and answers at once
Sign Body crypto HMAC-SHA256 of the parsed body with the Cal.com secret
Verify & Parse code Checks the signature, drops other events, normalizes the booking
Is New Booking? if BOOKING_CREATED or not
Find Duplicate httpRequest CRM Interaction with the same Booking UID
Get Lead Sources httpRequest All rows of CRM Automation: Lead Source (first page)
Find Person (CRM) httpRequest CRM People mirror by e-mail
Resolve code Stops on duplicate, matches the seller, prepares the Core person
Needs Core? if No CRM person and an e-mail exists
Find Person (Core) httpRequest Core People by e-mail
Exists in Core? if Core person found or not
Create Person (Core) httpRequest POST a Proposed person to Core People
Wait 1 min wait Waits one minute for the mirror sync
Recheck Person (CRM) httpRequest CRM People mirror by e-mail again
Sync Check code Counts attempts; done when found or after 10
Person Ready? if Leave the loop or wait again
Find Deals httpRequest The person’s CRM Deals
Prepare Deal code Picks the seller’s open Deal and its new stage, or builds a new Deal
Existing Deal? if Update or create
Update Deal httpRequest PATCH Deal Stage* of the reused Deal
Create Deal httpRequest POST a new Deal at Meeting Scheduled
Prepare Interaction code Builds the meeting Interaction
Create Interaction httpRequest POST the Interaction
Is Reschedule? if BOOKING_RESCHEDULED or cancellation/rejection
Find Original Meeting httpRequest CRM Interaction by the original Booking UID
Prepare Reschedule code Picks the Meeting booked Interaction and builds its patch
Original Found? if Original Meeting booked Interaction found or not
Move Meeting httpRequest PATCH the original Interaction
Find Booked Meeting httpRequest CRM Interaction by the current Booking UID
Prepare Cancellation code Picks the Meeting booked Interaction and builds its patch
Mark Meeting Cancelled httpRequest PATCH the Interaction to Meeting cancelled
Sticky Note stickyNote Setup note on the canvas; does not run

Connections

From Output To
Cal.com Webhook 0 Sign Body
Sign Body 0 Verify & Parse
Verify & Parse 0 Is New Booking?
Is New Booking? 0 Find Duplicate
Is New Booking? 1 Is Reschedule?
Find Duplicate 0 Get Lead Sources
Get Lead Sources 0 Find Person (CRM)
Find Person (CRM) 0 Resolve
Resolve 0 Needs Core?
Needs Core? 0 Find Person (Core)
Needs Core? 1 Find Deals
Find Person (Core) 0 Exists in Core?
Exists in Core? 0 Wait 1 min
Exists in Core? 1 Create Person (Core)
Create Person (Core) 0 Wait 1 min
Wait 1 min 0 Recheck Person (CRM)
Recheck Person (CRM) 0 Sync Check
Sync Check 0 Person Ready?
Person Ready? 0 Find Deals
Person Ready? 1 Wait 1 min
Find Deals 0 Prepare Deal
Prepare Deal 0 Existing Deal?
Existing Deal? 0 Update Deal
Existing Deal? 1 Create Deal
Update Deal 0 Prepare Interaction
Create Deal 0 Prepare Interaction
Prepare Interaction 0 Create Interaction
Is Reschedule? 0 Find Original Meeting
Is Reschedule? 1 Find Booked Meeting
Find Original Meeting 0 Prepare Reschedule
Prepare Reschedule 0 Original Found?
Original Found? 0 Move Meeting
Original Found? 1 Find Duplicate
Find Booked Meeting 0 Prepare Cancellation
Prepare Cancellation 0 Mark Meeting Cancelled

Settings and error handling

Setting Value
Execution order v1
Error workflow None
Retry on fail Off on every node
On error Stop the workflow (default) on every node
Caller policy Workflows from the same owner
Available in MCP Yes
Execution data retention Instance default; this workflow sets none

There is no schedule; the workflow runs only when Cal.com calls it. A failed Airtable call stops the execution where it is, after any earlier writes have already been saved.

Known gaps

These come from the node configuration. The Deal version was published on 2026-11-04 and tested only in a local simulation (four cases); no real booking has run through it yet. With the earlier Relationship version, three paths ran in n8n: a new booking for an existing person, a reschedule with the original found, and a cancellation.

Gap Effect
A reschedule whose original is not found reaches Find Duplicate without the duplicate formula Find Duplicate filters on an empty formula, Airtable returns the first Interaction of the table and Resolve stops. The rescheduled booking writes nothing
The webhook secret sits in the Sign Body parameters, not in an n8n credential Anyone who can open the workflow can read it, and a workflow export carries it
The signature is computed over the parsed body, not the raw request A payload whose re-serialized JSON differs from what Cal.com signed fails as an invalid signature
Cal.com gets 200 before the signature check A rejected or failed delivery looks successful in Cal.com; only n8n’s execution list shows the error
The duplicate check is a lookup, not a lock Two deliveries of the same booking at the same moment can both create records
Get Lead Sources and Find Deals read one page (up to 100 records) Above 100 Lead Source rows, or 100 Deals for one person, a match can be missed
A booking without e-mail, or a person still missing from the mirror after ten minutes, creates a Deal without People° and an Interaction without People° The Notes° line always says the person did not reach the mirror in time, even when the cause is a missing e-mail. A later booking from the same person creates another Deal
Only open Deals are reused A booking from a person whose Deal with that seller is Won, Lost or Abandoned creates a new Deal at Meeting Scheduled
Company° is never written on the Deal or the Interaction The Deal’s Name shows only the person until the seller links the company

Recover a partial write

Look at what was saved before you replay a delivery.

What was saved What to do
Core person created, CRM mirror still missing Wait for or fix the sync, then link the person in People° of the new Deal and of the Interaction
Deal written, Interaction failed Search Interaction by Booking UID before replaying; replay reuses the open Deal when it has People° and the same seller, and creates the Interaction
Two records for one Booking UID Keep one, move its links, then delete the other; nothing merges them automatically

Keep this page in sync

The box at the top shows the n8n version this page describes. Open the workflow in n8n: when its last save is newer than the date in the box, this page is out of date until the next review. Every node and every connection of that version is listed in Nodes and Connections.