Intermediate~20 min setupCommunication & CRMVerified April 2026
Slack logo
Close logo

How to Share Close Prospects to Slack with n8n

When a Close opportunity updates, n8n posts a formatted Slack message with company details, deal value, and conversation history so your team can collaborate before the next call.

Steps and UI details are based on platform versions at time of writing — check each platform for the latest interface.

Best for

Inside sales teams of 3-20 reps who do pre-call research together and need deal context in Slack without leaving the conversation

Not ideal for

Teams that want two-way sync where Slack replies update Close — that requires a separate reverse workflow and a bot user

Sync type

real-time

Use case type

notification

Real-World Example

💡

A 12-person SaaS sales team uses this to post into #prospect-research every time a Close opportunity moves to 'Discovery Scheduled' status. Before this, reps emailed each other asking 'anyone know anything about Acme Corp?' and got answers — if they got them at all — 2 hours before the call. Now the Slack post goes up the moment the deal updates, includes the last 3 email threads, and the team has context waiting before the rep even opens their calendar.

What Will This Cost?

Drag the slider to your expected monthly volume.

/mo
505005K50K

Each platform counts differently — Zapier: 1 task per trigger. Make: 1 operation per module per record. n8n: 1 execution per run.

Prices shown for annual billing. Based on published pricing as of April 2026.

Estimated ROI

1000

min saved/mo

$583

labor value/mo

Free

no platform cost

Based on ~2 min manual effort per operation at $35/hr fully loaded labor cost.

Implementation

Skip the setup

Import this workflow directly into n8n

Copy the pre-built n8n blueprint and paste it straight into n8n. All modules, filters, and field mappings are already configured — you just need to connect your accounts.

Before You Start

Make sure you have everything ready.

Close API key with read access to Leads, Opportunities, and Activities (generate under Settings > Your API Keys)
Slack bot app installed in your workspace with chat:write and channels:join OAuth scopes enabled
n8n instance running version 1.0+ either self-hosted or on n8n.cloud with Webhook node accessible from the public internet
Admin access to Close to create and edit webhook endpoints under Settings > Integrations
Slack channel created and named (e.g. #prospect-research) with at least one admin who can invite the bot

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Opportunity IDdata.id
Lead IDdata.lead_id
Opportunity Status Labeldata.status_label
Deal Valuedata.value
Company Namedisplay_name
6 optional fields▸ show
Company Websiteurl
Primary Contact Namecontacts[0].display_name
Primary Contact Titlecontacts[0].title
Recent Activity Summarydata[].type + data[].date_created + data[].body
Assigned Rep Namedata.assigned_to_name
Expected Close Datedata.date_won

Step-by-Step Setup

1

n8n Dashboard > Workflows > New Workflow

Create a new n8n workflow

Log in to your n8n instance at your self-hosted URL or n8n.cloud. Click the orange 'New Workflow' button in the top right of the Workflows dashboard. Give it a clear name like 'Close → Slack Prospect Research' so it's findable later. You'll land on an empty canvas with a single '+' node in the center.

  1. 1Click 'New Workflow' in the top-right corner
  2. 2Click the pencil icon next to 'My Workflow' and rename it to 'Close → Slack Prospect Research'
  3. 3Press Enter to save the name
What you should see: You see an empty workflow canvas with the name 'Close → Slack Prospect Research' displayed at the top.
2

Canvas > + Node > Webhook

Add a Webhook trigger node

Click the '+' node on the canvas to open the node picker. Search for 'Webhook' and select it. Set the HTTP Method to POST. n8n will generate a unique webhook URL — copy it now, you'll paste it into Close in the next step. Set Authentication to 'None' for now; Close webhooks don't support custom auth headers on the free tier, but you can add header validation later using n8n's Header Auth option.

  1. 1Click the '+' node on the canvas
  2. 2Type 'Webhook' in the search box and select the Webhook node
  3. 3Set HTTP Method to 'POST'
  4. 4Copy the 'Test URL' shown in the node panel — you'll use this in Close
What you should see: The Webhook node appears on the canvas showing a Test URL like 'https://your-n8n-instance.com/webhook-test/abc123'.
Common mistake — n8n shows two URLs: 'Test URL' and 'Production URL'. Use Test URL during setup to capture a live payload from Close. Switch to Production URL before going live — they are different endpoints.
n8n
+
click +
search apps
Slack
SL
Slack
Add a Webhook trigger node
Slack
SL
module added
3

Close > Settings > Integrations > Webhooks > Add Webhook

Configure the Close webhook

In Close, go to Settings > Integrations > Webhooks. Click 'Add Webhook'. Paste the n8n Test URL into the Endpoint URL field. Under 'Events', check 'opportunity.updated' — this fires whenever a deal changes status, value, or any field. You can also check 'opportunity.created' if you want new deals to trigger research posts immediately. Click Save.

  1. 1Navigate to Settings in the Close left sidebar
  2. 2Click 'Integrations', then select 'Webhooks'
  3. 3Click 'Add Webhook'
  4. 4Paste the n8n Test URL into the Endpoint URL field
  5. 5Check 'opportunity.updated' under Events, then click Save
What you should see: Close shows the new webhook in the list with a green status indicator and the endpoint URL you pasted.
Common mistake — Close sends webhooks for ALL opportunity updates — including minor ones like tag changes. If you only want to trigger on status changes to specific values (e.g., 'Discovery Scheduled'), you must filter in n8n in step 5. Without that filter, you'll get dozens of Slack posts per deal per day.
4

n8n Canvas > Webhook Node > Test Workflow

Capture a test payload from Close

Back in n8n, click 'Test Workflow' to put the Webhook node into listening mode. In Close, open an existing opportunity and make a small change — change the status or add $1 to the value — then save it. Switch back to n8n within 60 seconds. The Webhook node should show a green checkmark and display the raw JSON payload Close sent. Expand the data to see fields like opportunity_id, status_label, value, and lead_id.

  1. 1Click 'Test Workflow' at the top of the canvas
  2. 2Switch to Close and update any field on a test opportunity
  3. 3Return to n8n and wait for the green checkmark to appear on the Webhook node
  4. 4Click the Webhook node to inspect the incoming JSON payload
What you should see: The Webhook node shows 'Data received' with a JSON tree containing fields like opportunity_id, lead_id, status_label, value, and updated_by.
n8n
▶ Run once
executed
Slack
Close
Close
🔔 notification
received
5

Canvas > + Node > IF

Add an IF node to filter by status

Click '+' after the Webhook node and add an 'IF' node. Set Condition to: Value 1 = the expression '{{ $json.data.status_label }}', Operation = 'Equal', Value 2 = the status name you care about — for example, 'Discovery Scheduled'. This prevents every minor field update from flooding your Slack channel. Connect the 'True' branch to the next node. Ignore the 'False' branch — n8n will stop execution there automatically.

  1. 1Click '+' after the Webhook node
  2. 2Search for 'IF' and select the IF node
  3. 3Click 'Add Condition'
  4. 4Set Value 1 to '{{ $json.data.status_label }}' using the expression editor
  5. 5Set Operation to 'Equal' and Value 2 to your target status, e.g. 'Discovery Scheduled'
What you should see: The IF node shows two output branches labeled 'true' and 'false'. With your test payload loaded, the 'true' branch shows 1 item if the status matches.
Common mistake — Status labels in Close are case-sensitive in webhook payloads. 'discovery scheduled' will not match 'Discovery Scheduled'. Copy the exact string from Close's pipeline settings to avoid silent mismatches.
Slack
SL
trigger
filter
Status
matches criteria?
yes — passes through
no — skipped
Close
CL
notified
6

Canvas > + Node > HTTP Request

Fetch full lead details from Close API

The webhook payload contains an opportunity_id and lead_id but not the full company name, website, or contact details. Add an HTTP Request node to fetch the complete lead record. Set Method to GET, URL to 'https://api.close.com/api/v1/lead/{{ $json.data.lead_id }}/', and under Authentication choose 'Basic Auth' with your Close API key as the username and a blank password. This returns the full lead object with contacts, company details, and activity counts.

  1. 1Click '+' after the IF node's true branch
  2. 2Search for 'HTTP Request' and select it
  3. 3Set Method to 'GET'
  4. 4Set URL to 'https://api.close.com/api/v1/lead/{{ $json.data.lead_id }}/'
  5. 5Under Authentication, select 'Basic Auth', enter your Close API key as Username, leave Password blank
What you should see: The HTTP Request node returns a JSON object with fields including display_name, url, description, contacts, and opportunities arrays.
Common mistake — Close's REST API rate limit is 60 requests per minute on most plans. If you have high deal velocity, add a Wait node (set to 1 second) before this HTTP Request node to avoid 429 errors during busy periods.
7

Canvas > + Node > HTTP Request (second)

Fetch recent activity from Close API

Add a second HTTP Request node to pull the last 5 activities (emails, calls, notes) for this lead. Set Method to GET and URL to 'https://api.close.com/api/v1/activity/?lead_id={{ $node["HTTP Request"].json["id"] }}&_limit=5'. Use the same Basic Auth credentials. This gives you the conversation history you'll include in the Slack message so your team has real context — not just the deal value.

  1. 1Click '+' after the first HTTP Request node
  2. 2Add another HTTP Request node
  3. 3Set Method to 'GET'
  4. 4Set URL to 'https://api.close.com/api/v1/activity/?lead_id={{ $node["HTTP Request"].json["id"] }}&_limit=5'
  5. 5Apply the same Basic Auth credentials as the previous node
What you should see: The second HTTP Request node returns a 'data' array with up to 5 activity objects, each containing type (email, call, note), date_created, and a body or note field.
8

Canvas > + Node > Code

Build the Slack message with a Code node

Add a Code node after the second HTTP Request. This is where you format the Slack Block Kit message combining data from the webhook, lead record, and activity history. Use $input.all() to access all previous node outputs by name. The Code node lets you produce a single clean message object with the lead name, deal value, company URL, contact names, and a bulleted activity summary. Paste the code from the Pro Tip section below.

  1. 1Click '+' after the second HTTP Request node
  2. 2Search for 'Code' and select the Code node
  3. 3Set Language to 'JavaScript'
  4. 4Paste the Pro Tip code into the code editor
  5. 5Click 'Test Step' to verify the output includes a formatted 'blocks' array
What you should see: The Code node outputs a JSON object with a 'channel', 'text', and 'blocks' array ready for the Slack node to consume.
Common mistake — The Code node in n8n runs in a sandboxed Node.js environment. You cannot use require() for external npm packages unless you have configured the N8N_NODE_FUNCTION_ALLOW_EXTERNAL environment variable on your instance. Built-in JS methods are fine.

This Code node runs after both Close API calls and formats all the data into a Slack Block Kit message. Paste it into the Code node (step 8) replacing all default content. It reads from the Webhook node, the first HTTP Request (lead details), and the second HTTP Request (activities) by node name.

JavaScript — Code Node// n8n Code Node — Close Prospect Research → Slack Block Kit
▸ Show code
// n8n Code Node — Close Prospect Research → Slack Block Kit
// Reads from: Webhook node, 'Fetch Lead' HTTP node, 'Fetch Activities' HTTP node
const webhookData = $node['Webhook'].json.data;

... expand to see full code

// n8n Code Node — Close Prospect Research → Slack Block Kit
// Reads from: Webhook node, 'Fetch Lead' HTTP node, 'Fetch Activities' HTTP node

const webhookData = $node['Webhook'].json.data;
const lead = $node['Fetch Lead'].json;
const activitiesResponse = $node['Fetch Activities'].json;
const activities = activitiesResponse.data || [];

// Format deal value from cents to dollars
const dealValue = webhookData.value
  ? '$' + (webhookData.value / 100).toLocaleString('en-US', { minimumFractionDigits: 0 })
  : 'Value not set';

// Get primary contact
const primaryContact = lead.contacts && lead.contacts.length > 0
  ? `${lead.contacts[0].display_name || 'Unknown'}${lead.contacts[0].title ? ', ' + lead.contacts[0].title : ''}`
  : 'No contact on record';

// Strip HTML and truncate activity bodies
function cleanBody(text, maxLength = 180) {
  if (!text) return 'No content';
  const stripped = text.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
  return stripped.length > maxLength ? stripped.substring(0, maxLength) + '...' : stripped;
}

// Build activity summary lines (last 3 activities)
const activityLines = activities.slice(0, 3).map(act => {
  const typeEmoji = act._type === 'Email' ? '📨' : act._type === 'Call' ? '📞' : '📝';
  const date = act.date_created ? act.date_created.substring(0, 10) : 'Unknown date';
  const preview = cleanBody(act.body || act.note || act.subject || '');
  return `${typeEmoji} *${act._type}* (${date}): ${preview}`;
});

const activityText = activityLines.length > 0
  ? activityLines.join('\n')
  : '_No recent activity recorded_';

// Deep link to Close opportunity
const closeUrl = `https://app.close.com/opportunity/${webhookData.id}/`;

// Assemble Block Kit blocks
const blocks = [
  {
    type: 'header',
    text: {
      type: 'plain_text',
      text: `🔍 Research Request: ${lead.display_name || 'Unknown Company'}`,
      emoji: true
    }
  },
  {
    type: 'section',
    fields: [
      { type: 'mrkdwn', text: `*Status:*\n${webhookData.status_label}` },
      { type: 'mrkdwn', text: `*Deal Value:*\n${dealValue}` },
      { type: 'mrkdwn', text: `*Assigned Rep:*\n${webhookData.assigned_to_name || 'Unassigned'}` },
      { type: 'mrkdwn', text: `*Website:*\n${lead.url ? `<${lead.url}|${lead.url}>` : 'Not on record'}` }
    ]
  },
  {
    type: 'section',
    text: { type: 'mrkdwn', text: `*Primary Contact:*\n${primaryContact}` }
  },
  { type: 'divider' },
  {
    type: 'section',
    text: { type: 'mrkdwn', text: `*Recent Activity:*\n${activityText}` }
  },
  { type: 'divider' },
  {
    type: 'section',
    text: {
      type: 'mrkdwn',
      text: '💬 *Drop your research, intel, or talking points as a thread reply below.*'
    },
    accessory: {
      type: 'button',
      text: { type: 'plain_text', text: 'Open in Close', emoji: true },
      url: closeUrl,
      action_id: 'open_close_opportunity'
    }
  }
];

return [{
  json: {
    channel: '#prospect-research',
    text: `Research request: ${lead.display_name} — ${webhookData.status_label}`,
    blocks: blocks
  }
}];
9

Canvas > + Node > Slack > Message > Post

Add and configure the Slack node

Add a Slack node after the Code node. For Resource, select 'Message'. For Operation, select 'Post'. Connect your Slack credentials using OAuth2 — click 'Create New Credential' and follow the OAuth flow to authorize your workspace. Set Channel to your target channel name, e.g. '#prospect-research'. For the message content, switch to 'Send Blocks' mode and reference '{{ $json.blocks }}' from the Code node output to send the fully formatted Block Kit message.

  1. 1Click '+' after the Code node
  2. 2Search for 'Slack' and select it
  3. 3Set Resource to 'Message' and Operation to 'Post'
  4. 4Click 'Create New Credential' and complete the Slack OAuth2 authorization
  5. 5Set Channel to '#prospect-research' (or your channel name)
  6. 6Enable 'Blocks' toggle and set value to '{{ $json.blocks }}'
What you should see: The Slack node shows a green checkmark after testing and a formatted Block Kit message appears in your target Slack channel.
Common mistake — The Slack bot must be invited to the channel before it can post. If you see error 'not_in_channel', open Slack, go to #prospect-research, type /invite @YourBotName, and re-run the test.
10

Node Settings > On Error > Continue (for HTTP nodes) | Canvas > Error Branch > Slack

Add error handling with a node fallback

Click on each HTTP Request node and enable the 'Continue on Fail' option in the node settings panel. Then add a second Slack node connected to the error output of your main Slack node — set its channel to '#sales-ops-alerts' and message to 'Close→Slack workflow failed for lead {{ $json.data.lead_id }}. Check n8n execution log.' This way a Close API timeout doesn't silently kill the research post without anyone knowing.

  1. 1Click each HTTP Request node, open Settings tab, set 'On Error' to 'Continue'
  2. 2Click the red error dot on the main Slack node and drag to a new Slack node
  3. 3Configure the error Slack node to post to '#sales-ops-alerts'
  4. 4Set the error message text using the expression '{{ $json.message }}'
What you should see: The canvas shows a red error-branch connection from the main Slack node to the fallback Slack node. Test by temporarily entering an invalid Close API key.
11

n8n Canvas > Top-right toggle > Active | Close > Settings > Webhooks > Edit

Activate the workflow with the production webhook URL

Go back to your Webhook node and copy the 'Production URL' (different from the Test URL you used earlier). In Close, edit the webhook you created in step 3 and replace the Test URL with this Production URL. Back in n8n, toggle the workflow from 'Inactive' to 'Active' using the switch in the top right of the canvas. The workflow now runs automatically without you clicking 'Test Workflow' each time.

  1. 1Click the Webhook node and copy the 'Production URL'
  2. 2In Close, go to Settings > Integrations > Webhooks and click Edit on your webhook
  3. 3Replace the Test URL with the Production URL and click Save
  4. 4In n8n, click the Inactive/Active toggle in the top right to activate the workflow
What you should see: The toggle shows 'Active' in green. Update a Close opportunity to your target status and confirm the Slack message posts within 10-15 seconds.
Common mistake — Once activated, the workflow runs on every matching Close event — including events triggered by other automations or API calls, not just manual rep actions. If you have other Close automations updating opportunity status, they will also trigger Slack posts.

Going live

Production Checklist

Before you turn this on for real, confirm each item.

Troubleshooting

Common errors and how to fix them.

Frequently Asked Questions

Common questions about this workflow.

Analysis

VerdictWhy n8n for this workflow

Use n8n for this if your team already self-hosts infrastructure and wants full control over what data gets logged and where. The two-API-call pattern here (one for lead details, one for activity history) works cleanly in n8n because you can reference any previous node's output by name with $node['NodeName'].json — no workarounds needed. You also get the Code node for real formatting logic, which matters when you're building Block Kit messages with conditional fields and stripped HTML. The one scenario where you'd pick a different platform: if your sales ops team has zero JavaScript experience and needs to change the message format themselves without a developer. In that case, Make's visual data mapping UI would let a non-coder update the Slack template by clicking fields instead of editing code.

Cost

The cost math is straightforward. This workflow runs 3 node executions per trigger: the IF check, the two HTTP Requests, and the Code + Slack nodes — call it 5 executions total per Close event. At 50 deal updates per month that match your status filter, that's 250 executions monthly. n8n Cloud's Starter plan gives you 2,500 executions per month for $20/month, making this effectively free until you hit ~500 matching deal events per month. Self-hosted n8n has no execution limits — just your server cost. Compare that to Zapier, where this workflow would be a multi-step Zap counting as 4 tasks per run — 50 events would consume 200 tasks, burning through the free 100-task tier in two weeks.

Tradeoffs

Zapier has one genuine advantage here: its Close integration is pre-built with a 'New Opportunity' trigger that requires zero webhook configuration in Close — just click and authenticate. Setup is 8 minutes versus 20 minutes in n8n. Make's strength is the HTTP module with built-in pagination controls, which matters if you later expand to pulling 50+ activities per lead. Pipedream has a Close npm package and gives you full Node.js with real npm packages, so you could import a Slack Block Kit builder library and skip writing the block structure manually. Power Automate has no maintained Close connector at all — you'd be doing raw HTTP calls anyway, so there's no UI advantage over n8n. n8n is still the right call here because the self-hosted option eliminates per-task pricing entirely, and the Code node handles the HTML stripping and message formatting that would require multiple Zapier Formatter steps or Make modules.

Three things you'll hit after going live. First, Close's webhook delivery is not guaranteed to arrive in under 5 seconds — in practice it's usually 2-8 seconds, but during Close API incidents it can delay 5-10 minutes or retry up to 3 times. Your Slack message might appear late or in triplicate. The duplicate fix in troubleshooting entry 5 handles this. Second, the activity API endpoint returns activities in reverse-chronological order, but the _type field uses inconsistent casing across activity types — sometimes 'Email', sometimes 'EmailThread'. Your Code node regex should normalize this with a toLowerCase() check or it will display raw inconsistent labels. Third, Close deals with contacts that have multiple email addresses return all of them in the contacts[0].emails array — if you try to display contacts[0].emails directly in Slack you'll get '[object Object]'. Always map to contacts[0].emails[0].email for the primary address.

Ideas for what to build next

  • Post a digest of open research requests dailyAdd a second scheduled workflow that runs at 8 AM each morning, queries Close for all opportunities in 'Discovery Scheduled' status updated in the last 24 hours, and posts a single digest message to Slack — instead of individual pings throughout the day.
  • Write Slack thread replies back to Close as notesBuild a reverse workflow using Slack's Events API to capture thread replies on the research posts and write them back to the Close lead as notes via POST to api.close.com/api/v1/activity/note/ — so insights don't stay locked in Slack.
  • Add LinkedIn and Clearbit enrichment between Close fetch and Slack postInsert an HTTP Request node to Clearbit's Enrichment API between the Close lead fetch and the Code node — pass the company domain and append employee count, industry, and funding stage to the Slack message so the team gets enriched data they can't see in Close.

Related guides

Was this guide helpful?
Slack + Close overviewn8n profile →