Softechinfra
Productivity

n8n + Calendly + HubSpot: Auto-Brief Sales Reps Before Every Demo

A Calendly-trigger to Claude-brief to Slack-DM workflow that arms your AEs 30 min before every call. Full prompt, sample output, and the rules we run.

Softechinfra TeamSoftechinfra Team
April 18, 202613 min read
n8n + Calendly + HubSpot: Auto-Brief Sales Reps Before Every Demo

Our AEs walk into demos with a one-page brief: who the prospect is, what their company does, the last 3 form fills, signal from their public site, the ICP score, and a suggested opening question. That brief lands as a Slack DM exactly 30 minutes before the call. The workflow that builds it: Calendly webhook → HubSpot lookup → Apollo refresh → Claude Sonnet generates the brief → Slack DM. 16 nodes on n8n v1.121. AE prep time dropped from ~12 minutes to under 2 minutes of skim.

30 min
Pre-call DM delivery target
10 min
AE prep time saved per call
16
n8n nodes

TL;DR

A prospect books a demo via your Calendly link. Calendly fires an invitee.created webhook to n8n. The workflow grabs the invitee's email, looks them up in HubSpot, refreshes their Apollo data if last enriched >30 days ago, scrapes their company homepage for fresh signal, and asks Claude Sonnet 4.5 to write a 250-word brief. The brief is queued via the n8n Wait node to fire 30 minutes before the meeting starts, posted as a Slack DM to the AE.

Why this matters now

Two changes made this practical in 2026. Calendly's v2 API stabilised the invitee.created webhook payload with consistent event URIs (their April 2026 update fixed a long-standing bug where the event time was sometimes UTC and sometimes invitee-local). And n8n's Wait node v1.7 (March 2026) added an absolute-time mode — you can now say "wait until 30 min before this exact ISO timestamp" without writing a sleep-and-poll loop.

Before this, our AEs used a manual Notion template, copy-pasted LinkedIn snippets, and skipped prep when the day got busy. The skip rate was ~38% based on a 4-week audit. After 8 weeks on the automated brief, "I went in cold" complaints dropped to zero.

The 4-step workflow

📅
Step 1: Calendly webhook
invitee.created fires. We extract email, event URI, scheduled start time, and which AE owns the meeting.
🔎
Step 2: Multi-source enrich
HubSpot lookup, Apollo refresh (if stale), and a homepage scrape to surface today's hero copy.
📝
Step 3: Claude brief generation
Sonnet 4.5 writes a 250-word, opinionated, AE-tone brief with one suggested opener.
📨
Step 4: Wait then Slack DM
n8n Wait until T-30. Slack DM to the AE with the brief and a one-click "open HubSpot" link.

Prerequisites

  • n8n v1.121+ (the Wait node absolute-time mode shipped in v1.118)
  • Calendly Standard plan or higher (webhooks require it as of April 2026)
  • HubSpot account with API access, contacts module populated
  • Apollo.io account, same setup as Post 2
  • Anthropic API key with Claude Sonnet 4.5
  • Slack bot token with chat:write and users:read.email scopes
  • A mapping table (Google Sheet or n8n Set node) of AE name → Slack user ID

Step 1 — Calendly webhook

Create a Calendly webhook subscription pointing at https://n8n.softechinfra.com/webhook/calendly-booked. The signing key Calendly issues becomes your CALENDLY_WEBHOOK_SECRET env var.

Webhook node config:

json
{
    "parameters": {
      "httpMethod": "POST",
      "path": "calendly-booked",
      "responseMode": "responseNode",
      "options": {
        "rawBody": true
      }
    },
    "id": "calendly-webhook-001",
    "name": "Calendly Booked",
    "type": "n8n-nodes-base.webhook",
    "typeVersion": 2,
    "position": [240, 300]
  }

Verify Calendly's signature first. Calendly signs with HMAC-SHA256 over the raw body, header Calendly-Webhook-Signature looks like t=1716200400,v1=abcdef....

javascript
const crypto = require('crypto');
  const secret = $env.CALENDLY_WEBHOOK_SECRET;
  const header = $json.headers['calendly-webhook-signature'] || '';
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
  const expected = crypto.createHmac('sha256', secret)
    .update(parts.t + '.' + $json.body)
    .digest('hex');
  if (expected !== parts.v1) throw new Error('Invalid Calendly signature');
  
  const payload = JSON.parse($json.body);
  const event = payload.payload;
  return {
    json: {
      invitee_email: event.email,
      invitee_name: event.name,
      event_uri: event.event,
      invitee_uri: event.uri,
      meeting_start_iso: event.scheduled_event?.start_time,
      meeting_end_iso: event.scheduled_event?.end_time,
      ae_email: event.scheduled_event?.event_memberships?.[0]?.user_email,
      answers: event.questions_and_answers || []
    }
  };

Then immediately respond 200 OK to Calendly so they do not retry. The brief work happens asynchronously.

The event.questions_and_answers array contains anything the prospect typed in the Calendly form (company name, "what are you hoping to discuss"). This is gold for the Claude prompt later.

Step 2 — Multi-source enrich

Three parallel HTTP calls then a Merge node:

a) HubSpot lookup by email — the native HubSpot node, operation get by email. Pulls the contact record, the company association, all past form-fill data, and any icp_score / icp_tier from Post 2's workflow.

b) Apollo refresh — same call as Post 2, but only fired if apollo_match_at is older than 30 days. A Set node decides:

javascript
const lastMatched = $('Get HubSpot Contact').first().json.apollo_match_at;
  const stale = !lastMatched || (Date.now() - new Date(lastMatched).getTime()) > 30 * 86400 * 1000;
  return { json: { stale } };

An IF node routes accordingly. Saves us ~40% of Apollo credits on repeat prospects.

c) Homepage scrape — HTTP Request to the company's homepage (extracted from HubSpot's website property), with a Set node trimming to the first 4,000 chars of visible text. We use a lightweight HTML-to-text Code node:

javascript
const html = $json.body;
  const text = html
    .replace(/<script[\s\S]*?<\/script>/gi, '')
    .replace(/<style[\s\S]*?<\/style>/gi, '')
    .replace(/<[^>]+>/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()
    .slice(0, 4000);
  return { json: { homepage_text: text } };

For 95% of company homepages this captures the hero text, value prop, and recent product names. For JS-heavy SPAs (which load content async), we fall back to a Browserless headless-Chrome call — that adds a small cost per brief and we only fire it for known-SPA hosts.

A Merge node combines all three streams into a single object passed to Claude.

Step 3 — Claude generates the brief

The system prompt is the most important part of this workflow. It is opinionated — we want a brief written like Anjali (our top AE) would write it for herself, not a generic blob.

code
You are writing a 250-word pre-call brief for an Indian AE at Softechinfra (we sell custom software, AI automation, and n8n workflow builds to Indian SMBs).
  
  The reader is the AE. The voice is direct, slightly informal, no buzzwords. Use Indian English (₹ for currency, "lakh"/"crore" if amounts come up).
  
  Structure exactly:
  1. *Who is on the call* — name, title, company, headcount, industry. 2 lines max.
  2. *What we know about them* — last 3 HubSpot signals (form fills, page visits) and any Calendly answer they typed. 3-5 lines.
  3. *What's hot on their homepage today* — one sentence summarising the homepage hero copy. Skip if generic.
  4. *ICP read* — their tier + one-line reasoning (pulled from HubSpot icp_score).
  5. *Suggested opener* — one question the AE should lead with. Should reference something specific from sections 2 or 3.
  
  Banned phrases: "leverage", "robust", "synergy", "explore", "deep dive". Use plain words.
  
  Length: 220-260 words including the markdown headers. Output as Slack-flavoured markdown (uses *bold*, _italic_, > for quote blocks).

User message body (built from the merge):

code
Contact: {{ $json.invitee_name }}, {{ $json.person.title }} at {{ $json.organization.name }}.
  Company size: {{ $json.organization.estimated_num_employees }}. Industry: {{ $json.organization.industry }}.
  HubSpot signals (most recent 5):
  {{ $json.hubspot_activities.slice(0,5).map(a => '- ' + a.date + ': ' + a.type + ' ' + a.summary).join('\n') }}
  
  Calendly answers they typed:
  {{ $json.answers.map(qa => 'Q: ' + qa.question + '\nA: ' + qa.answer).join('\n\n') }}
  
  Their ICP score in our CRM: {{ $json.icp_score }} (tier {{ $json.icp_tier }}). Reasoning: {{ $json.icp_reasoning }}.
  
  Their homepage (first 4000 chars of visible text):
  {{ $json.homepage_text }}

Sonnet 4.5 input: ~3,500 tokens. Output: ~320 tokens.

Sample output (real, anonymised)

This is a brief generated last Tuesday for an AE prepping a 4pm call:

Who's on the call
Karthik Subramanian, Head of Engineering at Magpie Logistics (Chennai, ~75 ppl, 3PL warehousing + last-mile).
>
What we know about them
Karthik signed up to our newsletter in February, downloaded our "n8n for ops teams" PDF two weeks ago, and visited the /services/ai-automation page 3 times this month. On the Calendly form he wrote: _"We have an exception-handling bottleneck — every undelivered shipment triggers 4 manual calls a day. Looking for n8n + Twilio routing options."_
>
What's hot on their homepage today
Magpie just announced a Chennai-Tirupati corridor expansion and is hiring ops managers — they are scaling fast.
>
ICP read
Tier A (87/100). 50-200 ppl logistics, Indian-domiciled, founder-led — straight in the wheelhouse.
>
Suggested opener
"Karthik, before the deck — that exception-handling number you mentioned. Walk me through what happens after the 4th call. I want to understand where the human time actually goes."

That brief is 246 words, takes ~30 seconds to read, and arms Anjali to skip the "tell me about your business" intro and go straight to a question that flags she did the homework.

Step 4 — Wait then Slack DM

This is the part most n8n tutorials skip. You do not want to DM the brief right after booking — the call might be 4 days away. You want the DM 30 minutes before the call.

n8n Wait node v1.7 absolute-time mode:

json
{
    "parameters": {
      "resume": "specificTime",
      "dateTime": "={{ DateTime.fromISO($json.meeting_start_iso).minus({ minutes: 30 }).toISO() }}",
      "options": {}
    },
    "id": "wait-til-tminus30",
    "name": "Wait until T-30min",
    "type": "n8n-nodes-base.wait",
    "typeVersion": 1.7,
    "position": [1280, 300]
  }

The Wait node pauses the execution and resumes precisely at the computed timestamp. If the meeting is cancelled before then (we listen for invitee.canceled in a sister workflow), we kill the in-flight execution via the n8n REST API.

Slack DM node, using the im channel to the AE's user ID (looked up via the email → ID mapping):

code
:wave: *Pre-call brief — {{ $json.invitee_name }} ({{ $json.organization.name }})*
  _Call starts in 30 min: {{ DateTime.fromISO($json.meeting_start_iso).setZone('Asia/Kolkata').toFormat('h:mm a') }} IST_
  
  {{ $json.brief_markdown }}
  
  :hubspot: <https://app.hubspot.com/contacts/{{ $env.HUBSPOT_PORTAL_ID }}/contact/{{ $json.hubspot_id }}|Open in HubSpot> · :linkedin: <{{ $json.person.linkedin_url }}|LinkedIn>

The AE gets a single message that they can read on phone or laptop. Most of our team reads on phone walking back from lunch — designed for that.

Cost comparison vs the alternatives

For a 2-AE team taking ~22 demos a week (90/month):

n8n + Claude brief Clay.com workflow Lavender or Apollo AI Brief Manual AE prep × 10 min × 90 demos

When NOT to do this

You take fewer than 5 demos a week. Manual prep is faster to build the muscle than the automation, and the AE memory of context is better. Cross 10 demos/week before this earns its keep.

Your AEs prefer their own format. Forcing a standardised brief on a senior rep who has their own prep system creates noise, not value. We tested with our team for 2 weeks and let them edit the prompt — final version was 60% the AE's voice, 40% the original.

Your sales motion is high-velocity inbound. If every demo is 15 minutes and you do 30 a day, the brief is overhead. The workflow shines for considered 45-60 min discovery calls where context matters.

Calendly v1 deprecation: The legacy v1 webhook payload differs from v2. If your Calendly was created before 2020 you might be on v1 — check at calendly.com/integrations/api_webhooks. Upgrade before April 2026 when v1 fully sunsets, or your workflow stops firing without warning.

A real case — our own AE team, March-April 2026

Anjali and Rohit, our two AEs covering Indian SMB inbound. Combined ~22 demos a week. Pre-workflow numbers (4-week baseline):

  • "I went in cold" Slack flag rate: 38%
  • Demo-to-second-meeting conversion: 41%
  • Notes-to-CRM lag (median): 2.3 days
  • Post-workflow (8 weeks live):

  • "I went in cold" flag: 0% (they get the brief, they read the brief)
  • Demo-to-second-meeting conversion: 52%
  • Notes-to-CRM lag: 0.4 days (because the brief contains a pre-filled "to update after call" Notion link)
  • We are not claiming the brief alone moved the conversion 11 points. But we audited: 7 of those incremental wins were where the AE opened with the brief's suggested question and got a 2nd meeting because the prospect felt seen. That is 7 deals over 8 weeks; conservative ARR contribution ~₹14 lakh.

    Common mistakes

    Mistake 1 — Hardcoding the AE Slack ID. When a teammate leaves and the AE coverage rotates, you have to edit JSON. Use the email → Slack ID lookup via Slack's users.lookupByEmail instead.

    Mistake 2 — Generating the brief at booking time. If the demo is a week away, the homepage scrape is stale by call day. Generate at T-30, when it is fresh.

    Mistake 3 — Sending the brief as a public channel post. Even in an internal channel, this leaks signal. DM only. The AE owns the brief privately; nobody else needs to see it.

    Mistake 4 — Not respecting reschedules. If the prospect reschedules, Calendly fires invitee.canceled for the old slot + invitee.created for the new. Build a sister workflow that listens for invitee.canceled and kills the queued execution via the n8n executions DELETE API.

    Mistake 5 — Letting Claude name-drop competitors. Hard-fence in the system prompt: "Do not mention competitor product names by default. Only reference them if the homepage_text mentions them explicitly." We had an early version that talked about Zapier in every brief because Claude pattern-matched to "automation."

    FAQ

    What if the meeting is in 15 minutes?

    The Wait node fires immediately if the target time is in the past. So if a prospect books 10 minutes before the slot, the brief lands within ~6 seconds of booking. Good enough.

    Can the brief include Gong or recording-call snippets?

    Yes. If you have Gong / Chorus / Fireflies, add a parallel HTTP Request to their API and pull the last call summary with that prospect's domain. Adds ~5 seconds and 200 tokens.

    How do I customise the brief for different deal types?

    Add a Switch node before Claude based on HubSpot deal_type (new business / expansion / renewal). Each branch has a slightly different system prompt. We have three variants in production.

    Does Slack DM work to external guests on the same call?

    No — DMs are workspace-internal. If a customer success rep and an AE are both on the call, DM both. If a partner from another company is involved, send them an email instead via the n8n Email Send node.

    Can I extend this to post-call notes capture?

    That is a separate sister workflow. Listen for the calendar event end, ping the AE 5 minutes after for a 60-second voice memo, transcribe with Whisper, push structured notes back to HubSpot. We run it; it is roughly the same complexity.

    Does Calendly support multi-rep round robin?

    Yes — Calendly's team round-robin events fire the same webhook with the assigned AE in event_memberships[0]. The mapping in step 4 handles whoever was assigned automatically.

    What if HubSpot does not have the contact?

    The workflow has a branch: if HubSpot get by email returns 404, run the Apollo enrichment from Post 2 first, then create the contact in HubSpot, then continue. Adds ~3 seconds and an extra Apollo credit.

    Want this sales-prep automation built?

    We ship the full 16-node workflow above, wired to your Calendly events, your HubSpot/Pipedrive/Zoho, and your AEs' Slack DMs. Includes a brief-tone tuning session with your top rep so the output sounds like your team, not generic AI. Suitable if your team takes 8+ demos a week and prep time has become an exec-team complaint. No slides — bring three of your recent call recordings.

    Book a 20-min Call
    Tags:
    n8nCalendlyHubSpotSales AutomationClaudeSlackWorkflow Automation
    Share this post:
    Softechinfra Team

    Softechinfra Team

    Insights and updates from the Softechinfra team.