

How to Send Close Lead Assignments to Slack with Pipedream
When a lead is assigned in Close CRM, this automation fires instantly and posts a formatted message with prospect details to the assigned rep's dedicated Slack channel.
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 who assign leads in Close and need reps notified in Slack within seconds, not minutes.
Not ideal for
Teams without dedicated per-rep Slack channels — use a single #leads channel with Zapier instead to keep setup simple.
Sync type
real-timeUse case type
notificationReal-World Example
A 12-person SaaS sales team gets 80–120 inbound leads per week from three sources — demo requests, trials, and outbound lists. Before this workflow, managers assigned leads in Close and reps found out whenever they happened to open the CRM — sometimes 45 minutes later. Now each rep has a personal Slack channel like #leads-jenna, and the moment a lead hits their queue they get a message with company size, lead source, phone, and email. Average response time dropped from 38 minutes to under 6 minutes in the first week.
What Will This Cost?
Drag the slider to your expected monthly volume.
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
Import this workflow directly into Pipedream
Copy the pre-built Pipedream blueprint and paste it straight into Pipedream. 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.
Field Mapping
Map these fields between your apps.
| Field | API Name | |
|---|---|---|
| Required | ||
| Lead ID | lead_id | |
| Assigned To (User ID) | assigned_to | |
| Lead Name / Company | name | |
| Lead Source | lead_source | |
| Lead URL | url | |
4 optional fields▸ show
| Primary Email | contacts[0].emails[0].email |
| Primary Phone | contacts[0].phones[0].phone |
| Company Size | custom.lcf_{your_field_id} |
| Lead Status | status_label |
Step-by-Step Setup
pipedream.com > Workflows > New Workflow
Create a new Pipedream workflow
Go to pipedream.com and click 'New Workflow' in the top-right corner. Give it a descriptive name like 'Close Lead Assignment → Slack'. You'll land on the workflow canvas with an empty trigger slot at the top. This is where you define what event starts the workflow.
- 1Click 'New Workflow' in the top-right corner of the Pipedream dashboard
- 2Enter a name: 'Close Lead Assignment → Slack'
- 3Click 'Create' to open the workflow canvas
Workflow Canvas > Add a trigger > HTTP / Webhook > New Requests
Add a Close webhook trigger via HTTP
Close CRM does not have a native Pipedream source, so you'll use Pipedream's HTTP trigger to receive Close webhooks. Click 'Add a trigger', search for 'HTTP / Webhook', and select it. Choose 'New Requests' as the event type. Pipedream will generate a unique endpoint URL — copy it now, you'll paste it into Close in the next step.
- 1Click the grey 'Add a trigger' block
- 2Search for 'HTTP / Webhook' in the trigger search box
- 3Select 'HTTP / Webhook' from the results
- 4Choose 'New Requests' as the event type
- 5Copy the generated endpoint URL shown under 'Endpoint'
Close > Settings > Integrations > Webhooks > Add Webhook
Register the webhook in Close CRM
In Close, go to Settings > Integrations > Webhooks and add a new webhook pointing to your Pipedream endpoint URL. Set the event to 'lead.updated' — this fires whenever a lead's assigned user changes. You can also add 'lead.created' if you want to catch leads that arrive already assigned. Set the status to Active and save.
- 1In Close, click your org name in the top-left, then 'Settings'
- 2Navigate to 'Integrations' > 'Webhooks'
- 3Click 'Add Webhook'
- 4Paste your Pipedream endpoint URL into the 'URL' field
- 5Check the 'lead.updated' event (and optionally 'lead.created')
- 6Set Status to 'Active' and click 'Save'
Workflow Canvas > HTTP Trigger > Listen for Test Event
Test the trigger and inspect the payload
Back in Pipedream, click 'Listen for a test event' on your HTTP trigger step. In Close, either use the 'Send Test' button on the webhook or manually reassign a lead to a rep. Within 2–3 seconds, Pipedream will capture the incoming payload. Click on the event to expand it and review the JSON structure — you'll see fields like assigned_to, lead_id, and data.changes.
- 1Click 'Listen for a test event' in the trigger panel
- 2Switch to Close and reassign any lead to trigger the webhook
- 3Return to Pipedream and wait for the event to appear (2–5 seconds)
- 4Click the captured event to expand and inspect the JSON payload
Workflow Canvas > + > Run Node.js code
Add a Node.js code step to filter assignment changes
Click the '+' button below the trigger to add a new step. Select 'Run Node.js code'. This step checks whether the webhook fired because of an assignment change specifically — not a phone number edit or a note addition. If the assignment field didn't change, this step throws an error that stops the workflow without posting to Slack.
- 1Click the '+' below the trigger step
- 2Select 'Run Node.js code' from the step type list
- 3Paste the filter code into the code editor (see pro tip below)
- 4Click 'Test' to verify it passes on your test event
This code goes in a single Node.js step that combines the filter logic, Close API fetch, and Slack message construction into one step. Paste it after the HTTP trigger as your second step — it replaces steps 5, 6, and 8 if you want a leaner workflow with fewer steps to debug.
JavaScript — Code Stepimport axios from 'axios';▸ Show code
import axios from 'axios';
export default defineComponent({
async run({ steps, $ }) {... expand to see full code
import axios from 'axios';
export default defineComponent({
async run({ steps, $ }) {
const payload = steps.trigger.event.body;
// Step 1: Only process assignment changes
const changes = payload?.data?.changed_fields || [];
if (!changes.includes('assigned_to')) {
throw new Error(`Skipped: event '${payload.event}' did not change assigned_to. Changed fields: ${JSON.stringify(changes)}`);
}
const leadId = payload?.data?.id;
const assignedToId = payload?.data?.assigned_to;
if (!leadId || !assignedToId) {
throw new Error(`Missing lead ID or assigned_to in payload: ${JSON.stringify(payload?.data)}`);
}
// Step 2: Fetch full lead from Close API
let lead;
try {
const closeRes = await axios.get(
`https://api.close.com/api/v1/lead/${leadId}/`,
{
auth: { username: process.env.CLOSE_API_KEY, password: '' },
timeout: 5000,
}
);
lead = closeRes.data;
} catch (err) {
throw new Error(`Close API fetch failed for lead ${leadId}: ${err.message}`);
}
// Step 3: Resolve Slack channel for this Close user
const channelMap = {
'user_abc111': 'C0ABC123456', // Jenna Torres
'user_def222': 'C0DEF789012', // Marcus Webb
'user_ghi333': 'C0GHI345678', // Priya Nair
};
const slackChannelId = channelMap[assignedToId];
if (!slackChannelId) {
throw new Error(`No Slack channel mapped for Close user: ${assignedToId}. Add them to channelMap.`);
}
// Step 4: Extract lead details safely
const companyName = lead.name || 'Unknown Company';
const leadSource = lead.lead_source || 'Not specified';
const statusLabel = lead.status_label || 'Unknown';
const primaryEmail = lead.contacts?.[0]?.emails?.[0]?.email || 'No email on file';
const primaryPhone = lead.contacts?.[0]?.phones?.[0]?.phone || 'No phone on file';
const companySize = lead.custom?.lcf_YOUR_FIELD_ID || 'Not specified'; // Replace with your field ID
const leadUrl = `https://app.close.com/lead/${leadId}/`;
// Step 5: Post to Slack
const slackToken = process.env.SLACK_BOT_TOKEN;
const messagePayload = {
channel: slackChannelId,
text: `🔥 New lead assigned to you: *${companyName}*`,
blocks: [
{
type: 'header',
text: { type: 'plain_text', text: '🔥 New Lead Assigned to You', emoji: true },
},
{
type: 'section',
fields: [
{ type: 'mrkdwn', text: `*Company:*\n${companyName}` },
{ type: 'mrkdwn', text: `*Source:*\n${leadSource}` },
{ type: 'mrkdwn', text: `*Status:*\n${statusLabel}` },
{ type: 'mrkdwn', text: `*Company Size:*\n${companySize}` },
{ type: 'mrkdwn', text: `*Email:*\n${primaryEmail}` },
{ type: 'mrkdwn', text: `*Phone:*\n${primaryPhone}` },
],
},
{
type: 'actions',
elements: [
{
type: 'button',
text: { type: 'plain_text', text: 'Open in Close', emoji: true },
url: leadUrl,
style: 'primary',
},
],
},
],
};
try {
const slackRes = await axios.post(
'https://slack.com/api/chat.postMessage',
messagePayload,
{
headers: {
Authorization: `Bearer ${slackToken}`,
'Content-Type': 'application/json',
},
timeout: 5000,
}
);
if (!slackRes.data.ok) {
throw new Error(`Slack API error: ${slackRes.data.error}`);
}
return {
success: true,
channel: slackChannelId,
lead: companyName,
ts: slackRes.data.ts,
};
} catch (err) {
throw new Error(`Slack post failed for channel ${slackChannelId}: ${err.message}`);
}
},
});Workflow Canvas > + > Run Node.js code
Add a Close API step to fetch full lead details
The webhook payload from Close contains the lead ID but minimal prospect data. You need to call the Close API to get company name, company size, lead source, primary email, and primary phone. Click '+' to add a new step, select 'Run Node.js code', and use the Close REST API at https://api.close.com/api/v1/lead/{id}/. Authenticate using your Close API key via Pipedream's environment variables.
- 1Click '+' to add a step after the filter step
- 2Select 'Run Node.js code'
- 3Add your Close API key to Pipedream's environment variables under Settings > Environment Variables, key name: CLOSE_API_KEY
- 4Write the fetch call using the lead ID from the trigger payload
- 5Click 'Test' to verify the lead object returns with full contact data
Workflow Canvas > + > Run Node.js code
Resolve the assigned rep's Slack channel name
You know the Close user ID of the assignee, but you need to map that to a Slack channel. The cleanest approach is a hardcoded lookup object in Node.js code — a plain JavaScript object where Close user IDs map to Slack channel IDs. This is more reliable than querying Slack's API on every run and avoids rate limit issues. Add another Node.js code step and build this map.
- 1Click '+' to add a new code step
- 2Select 'Run Node.js code'
- 3Create a lookup object: { 'close_user_id_1': 'C0ABC123456', 'close_user_id_2': 'C0DEF789012' }
- 4Find Close user IDs in Close under Settings > Team > click each rep
- 5Find Slack channel IDs by right-clicking a channel in Slack > View Channel Details > copy the ID at the bottom
This code goes in a single Node.js step that combines the filter logic, Close API fetch, and Slack message construction into one step. Paste it after the HTTP trigger as your second step — it replaces steps 5, 6, and 8 if you want a leaner workflow with fewer steps to debug.
JavaScript — Code Stepimport axios from 'axios';▸ Show code
import axios from 'axios';
export default defineComponent({
async run({ steps, $ }) {... expand to see full code
import axios from 'axios';
export default defineComponent({
async run({ steps, $ }) {
const payload = steps.trigger.event.body;
// Step 1: Only process assignment changes
const changes = payload?.data?.changed_fields || [];
if (!changes.includes('assigned_to')) {
throw new Error(`Skipped: event '${payload.event}' did not change assigned_to. Changed fields: ${JSON.stringify(changes)}`);
}
const leadId = payload?.data?.id;
const assignedToId = payload?.data?.assigned_to;
if (!leadId || !assignedToId) {
throw new Error(`Missing lead ID or assigned_to in payload: ${JSON.stringify(payload?.data)}`);
}
// Step 2: Fetch full lead from Close API
let lead;
try {
const closeRes = await axios.get(
`https://api.close.com/api/v1/lead/${leadId}/`,
{
auth: { username: process.env.CLOSE_API_KEY, password: '' },
timeout: 5000,
}
);
lead = closeRes.data;
} catch (err) {
throw new Error(`Close API fetch failed for lead ${leadId}: ${err.message}`);
}
// Step 3: Resolve Slack channel for this Close user
const channelMap = {
'user_abc111': 'C0ABC123456', // Jenna Torres
'user_def222': 'C0DEF789012', // Marcus Webb
'user_ghi333': 'C0GHI345678', // Priya Nair
};
const slackChannelId = channelMap[assignedToId];
if (!slackChannelId) {
throw new Error(`No Slack channel mapped for Close user: ${assignedToId}. Add them to channelMap.`);
}
// Step 4: Extract lead details safely
const companyName = lead.name || 'Unknown Company';
const leadSource = lead.lead_source || 'Not specified';
const statusLabel = lead.status_label || 'Unknown';
const primaryEmail = lead.contacts?.[0]?.emails?.[0]?.email || 'No email on file';
const primaryPhone = lead.contacts?.[0]?.phones?.[0]?.phone || 'No phone on file';
const companySize = lead.custom?.lcf_YOUR_FIELD_ID || 'Not specified'; // Replace with your field ID
const leadUrl = `https://app.close.com/lead/${leadId}/`;
// Step 5: Post to Slack
const slackToken = process.env.SLACK_BOT_TOKEN;
const messagePayload = {
channel: slackChannelId,
text: `🔥 New lead assigned to you: *${companyName}*`,
blocks: [
{
type: 'header',
text: { type: 'plain_text', text: '🔥 New Lead Assigned to You', emoji: true },
},
{
type: 'section',
fields: [
{ type: 'mrkdwn', text: `*Company:*\n${companyName}` },
{ type: 'mrkdwn', text: `*Source:*\n${leadSource}` },
{ type: 'mrkdwn', text: `*Status:*\n${statusLabel}` },
{ type: 'mrkdwn', text: `*Company Size:*\n${companySize}` },
{ type: 'mrkdwn', text: `*Email:*\n${primaryEmail}` },
{ type: 'mrkdwn', text: `*Phone:*\n${primaryPhone}` },
],
},
{
type: 'actions',
elements: [
{
type: 'button',
text: { type: 'plain_text', text: 'Open in Close', emoji: true },
url: leadUrl,
style: 'primary',
},
],
},
],
};
try {
const slackRes = await axios.post(
'https://slack.com/api/chat.postMessage',
messagePayload,
{
headers: {
Authorization: `Bearer ${slackToken}`,
'Content-Type': 'application/json',
},
timeout: 5000,
}
);
if (!slackRes.data.ok) {
throw new Error(`Slack API error: ${slackRes.data.error}`);
}
return {
success: true,
channel: slackChannelId,
lead: companyName,
ts: slackRes.data.ts,
};
} catch (err) {
throw new Error(`Slack post failed for channel ${slackChannelId}: ${err.message}`);
}
},
});Workflow Canvas > + > Slack > Send a Message
Add a Slack step to post the notification message
Click '+' to add a new step and search for 'Slack'. Select 'Send a Message' as the action. Connect your Slack account via Pipedream's Connected Accounts panel — you'll authorize with OAuth and Pipedream will store the token. Set the channel to the dynamic channel ID output from step 7. Build the message using Block Kit or a plain text template that includes company name, lead source, company size, email, and phone.
- 1Click '+' to add a new step
- 2Search for 'Slack' and select it
- 3Choose 'Send a Message' as the action
- 4Click 'Connect a Slack account' and complete the OAuth flow
- 5Set the 'Channel' field to the dynamic channel ID from the previous step using the expression editor
- 6Build the message text using fields from the Close API response
channel: {{channel}}
ts: {{ts}}
Workflow Canvas > + > Run Node.js code (or Workflow Settings > Error Handling)
Add error handling for missing leads or unmapped reps
Add a final Node.js code step that only runs on error using Pipedream's error workflow feature, or add try/catch blocks inside each prior code step. At minimum, log failed events to a Google Sheet or send a fallback message to a #lead-assignment-errors Slack channel. This catches cases where a new rep joins the team but hasn't been added to the channel lookup map yet.
- 1Click the workflow name at the top to open Workflow Settings
- 2Scroll to 'Error Handling' and toggle on 'Send errors to another workflow'
- 3Or add try/catch blocks inside your code steps to catch and route errors
- 4Configure the error destination to post to a #lead-assignment-errors Slack channel
Workflow Canvas > Deploy (top-right button)
Deploy the workflow and run a live test
Click 'Deploy' in the top-right corner of the Pipedream canvas. Once deployed, the workflow runs on every incoming webhook — not just test events. Go to Close and reassign a real lead to a sales rep. Watch the Pipedream event history for the live run, and check the rep's Slack channel to confirm the message arrived with accurate data.
- 1Click the blue 'Deploy' button in the top-right
- 2Confirm deployment in the modal
- 3In Close, reassign a real lead to a rep who has a mapped Slack channel
- 4Open Pipedream's event history to watch the live execution
- 5Check the rep's Slack channel to verify the message content and formatting
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
Use Pipedream for this if your team has someone comfortable reading a Node.js stack trace and you want sub-5-second notification delivery with no polling lag. Pipedream's instant webhook processing means the Slack message lands within 2–4 seconds of the Close assignment — no 1-minute polling intervals. The second reason to pick Pipedream here is the code step: Close's custom field IDs vary by account, the assigned_to field is a raw user ID that needs mapping, and company size often sits in a nested custom object. Handling all of that in a no-code tool means stacking 4–5 action steps with fragile field expressions. In Pipedream, it's 20 lines of JavaScript. The one scenario where you'd pick something else: if your team has zero developers and the person setting this up will also be the one maintaining it alone for years — use Zapier instead and accept the 1–2 minute delay.
Pipedream charges credits per step execution. A run of this workflow hits 3–4 steps (trigger, filter, Close API call, Slack post). That's roughly 4 credits per run. At 100 lead assignments per month, you burn 400 credits. Pipedream's free tier includes 10,000 credits per month — so at that volume, this workflow costs you nothing. At 2,000 lead assignments per month (busy team), you're at 8,000 credits, still free. The paid tier at $19/month adds 50,000 credits if you scale past that. Compare to Zapier: their equivalent is a 3-step Zap that costs 1 task per run. At 2,000 assignments/month, you need Zapier's Professional plan at $49/month just for the task volume. Pipedream is cheaper by $30/month at this use case's typical volume.
Make handles the Close-to-Slack pattern with a cleaner visual router if you want to conditionally branch between rep channels based on deal size or territory — their Router module is better than Pipedream's if/else code approach for non-developers. Zapier has a native Close trigger called 'New Lead' that requires no webhook setup, which saves 15 minutes of configuration — but it polls every 5–15 minutes, so a hot inbound lead sits unnoticed for up to 15 minutes. n8n lets you self-host, which matters if your Close data can't touch third-party servers — Pipedream is cloud-only. Power Automate has no native Close connector, so you'd need a custom HTTP trigger anyway, and then you're writing Power Fx expressions for field mapping that's easier in JavaScript. Pipedream wins here specifically because Close has no native source and you need code to handle the custom field mess — writing that code is faster in Pipedream than fighting any visual tool's limitations.
Three things you'll hit after this goes live. First, Close fires 'lead.updated' on every field change — if a rep edits a phone number on a lead already assigned to them, the webhook fires and your filter step needs to catch it. The filter works, but watch your event history the first week to confirm the block rate looks right (expect 80–90% of events to be filtered out). Second, Slack's Block Kit button with a URL field only works if your Slack workspace has 'Unfurl links' turned off — otherwise the message expands the Close URL into a giant preview that buries the actual details. Turn off link unfurling in your Slack channel settings or use a plain text message format. Third, Close's API rate limit is 40 requests per second per API key, which you won't hit with this workflow, but if you're also running other Close API integrations on the same key, check your combined rate against that ceiling.
Ideas for what to build next
- →Add a Close activity log when the rep views the Slack message — Use Slack's interactivity features to add an 'I'm on it' button to the message — clicking it triggers a second Pipedream workflow that logs a note on the Close lead confirming the rep acknowledged the assignment.
- →Build a daily digest for uncontacted assigned leads — Add a second Pipedream workflow on a scheduled trigger that queries Close each morning for leads assigned in the last 24 hours with no activity logged, and posts a summary to the rep's Slack channel so nothing slips through.
- →Route hot leads differently based on company size — Add a branching code step that checks company size from the Close API response — leads from companies over 500 employees get posted to both the rep's personal channel and a shared #enterprise-leads channel for management visibility.
Related guides
How to Share Notion Meeting Notes to Slack with Pipedream
~15 min setup
How to Share Notion Meeting Notes to Slack with Power Automate
~15 min setup
How to Share Notion Meeting Notes to Slack with n8n
~20 min setup
How to Send Notion Meeting Notes to Slack with Zapier
~8 min setup
How to Share Notion Meeting Notes to Slack with Make
~12 min setup
How to Create Notion Tasks from Slack with Pipedream
~15 min setup