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

How to Send Copper Deal Stage Alerts to Slack with n8n

Polls Copper every few minutes for deal stage changes and posts a formatted Slack message to your sales channel with the deal name, new stage, owner, and value.

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

Best for

Small to mid-size sales teams (5–30 reps) who want real-time Slack visibility into pipeline movement without building a custom integration.

Not ideal for

Teams needing true instant triggers — Copper has no native webhooks, so there will always be a polling delay of 1–5 minutes.

Sync type

scheduled

Use case type

notification

Real-World Example

💡

A 12-person SaaS sales team uses this to post to #pipeline-wins and #deals-at-risk in Slack whenever a Copper deal moves to 'Proposal Sent', 'Negotiation', or 'Won'. Before this workflow, reps relied on a weekly standup to surface stage changes — deals stalled in 'Proposal Sent' for days before anyone noticed. Now a Slack message fires within 3 minutes of the stage update, and the team lead can jump in to help or celebrate immediately.

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.

Copper API key and account email — found in Copper under Settings > Integrations > API Keys. Use a dedicated service account, not a personal user.
Slack Bot Token (xoxb-...) with scopes: chat:write, channels:read, and groups:read if posting to private channels.
n8n instance running v1.0 or later — either n8n Cloud or self-hosted with access to the Remove Duplicates node (added in v1.0).
Slack bot added to the target notification channel — the bot must be a member of the channel before it can post.

Optional

Copper pipeline and stage IDs for the stages you want to monitor — collect these by calling GET /pipelines from the Copper API and noting the numeric IDs.

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Deal IDid
Deal Namename
Stage IDstage_id
Pipeline IDpipeline_id
Owner IDassignee_id
Updated Atdate_modified
3 optional fields▸ show
Deal Valuemonetary_value
Close Dateclose_date
Company Namecompany_name

Step-by-Step Setup

1

n8n Dashboard > Workflows > + New Workflow

Create a new n8n workflow

Log into your n8n instance (cloud at app.n8n.cloud or self-hosted). Click the '+ New Workflow' button in the top right of the Workflows dashboard. Give the workflow a name like 'Copper Deal Stage → Slack'. You'll land on a blank canvas with a default Start node — delete it, since you'll be using a Schedule trigger instead.

  1. 1Click '+ New Workflow' in the top right
  2. 2Type 'Copper Deal Stage → Slack' in the workflow name field at the top
  3. 3Click the existing Start node on the canvas and press Delete
What you should see: You should see a blank canvas with just the workflow name visible at the top and no nodes placed.
Common mistake — If you're on n8n Cloud free tier, you get 5 active workflows. Make sure you have a slot available before starting.
2

Canvas > + > Schedule Trigger > Interval

Add a Schedule trigger

Click the '+' button on the canvas to open the node picker. Search for 'Schedule Trigger' and select it. Set the interval to every 3 minutes — this is the polling frequency that checks Copper for changes. Every 1 minute is available but will hit Copper's API rate limit faster; every 5 minutes is safer for teams with high deal volume.

  1. 1Click the '+' icon on the empty canvas
  2. 2Type 'Schedule' in the search box
  3. 3Select 'Schedule Trigger'
  4. 4Set 'Trigger Interval' to 'Minutes'
  5. 5Set the value to '3'
What you should see: You should see a Schedule Trigger node on the canvas showing '3 minutes' in the node summary.
Common mistake — Copper's API rate limit is 600 requests per minute at the account level. If your team uses other Copper integrations simultaneously, polling every 1 minute may cause 429 errors.
n8n
+
click +
search apps
Slack
SL
Slack
Add a Schedule trigger
Slack
SL
module added
3

Copper Node > Credentials > Create New

Connect your Copper credentials

Click '+' after the Schedule Trigger to add a new node. Search for 'Copper' and select it. In the node configuration panel, click 'Credential for Copper API' and then 'Create New'. You'll need your Copper API key and the email address of your Copper account — both found in Copper under Settings > Integrations > API Keys. Paste them in and click Save.

  1. 1Click '+' after the Schedule Trigger node
  2. 2Search 'Copper' and select the Copper node
  3. 3Click 'Credential for Copper API' dropdown
  4. 4Click 'Create New Credential'
  5. 5Paste your Copper API Key and account email
  6. 6Click 'Save'
What you should see: The credential dropdown should now show your Copper account email and a green checkmark confirming the connection is valid.
Common mistake — The API key in Copper is per-user, not per-account. Use a dedicated integration user's key, not a personal one — if that user is deactivated, the workflow breaks silently.
4

Copper Node > Resource > Opportunity > Get All > Filters

Configure Copper to list recently modified deals

In the Copper node, set Resource to 'Opportunity' and Operation to 'Get All'. Enable the 'Return All' toggle so you retrieve every result, not just the first page. Under Filters, add a filter for 'Updated At' greater than or equal to the current time minus 5 minutes — this scopes each poll to only recently changed deals. Without this filter, you'll return your entire pipeline on every execution.

  1. 1Set Resource to 'Opportunity'
  2. 2Set Operation to 'Get All'
  3. 3Toggle 'Return All' to ON
  4. 4Click 'Add Filter'
  5. 5Set filter field to 'Updated At'
  6. 6Set operator to 'is after'
  7. 7Set value to '={{ new Date(Date.now() - 5 * 60 * 1000).toISOString() }}'
What you should see: When you click 'Test Step', you should see a list of deals modified in the last 5 minutes in the output panel on the right. If no deals were updated, you'll see an empty array — that's correct.
5

Canvas > + > Filter > Conditions

Filter to deals where stage actually changed

Copper returns all recently updated deals regardless of what changed — a note, a contact link, or an activity update will all appear here. You need to filter down to only deals where the pipeline stage changed. Add an n8n 'Filter' node after the Copper node. The problem: Copper's API doesn't return a 'previous stage' field, so you'll use a static data store to compare. For now, filter to deals where 'stage_id' is not null as a baseline — you'll add deduplication in the next step.

  1. 1Click '+' after the Copper node
  2. 2Search 'Filter' and select the Filter node
  3. 3Click 'Add Condition'
  4. 4Set the left value to '={{ $json.stage_id }}'
  5. 5Set operator to 'is not empty'
What you should see: The Filter node output should only pass through deal records that have a stage_id value populated. Deals with null stage will be dropped.
Common mistake — This filter alone won't prevent duplicate notifications. A deal updated for any reason — not just a stage change — will still pass through. The deduplication node in Step 6 is what actually prevents repeat messages.
Slack
SL
trigger
filter
Deal Stage
matches criteria?
yes — passes through
no — skipped
Copper
CO
notified
6

Canvas > + > Remove Duplicates > Mode

Deduplicate with n8n's built-in deduplication node

Add a 'Remove Duplicates' node after the Filter node. Set the mode to 'Remove Items Seen in Previous Executions'. Set the deduplication key to a composite value: the deal ID combined with the stage ID, like '={{ $json.id }}-{{ $json.stage_id }}'. This ensures a notification only fires once per deal-per-stage combination. If a deal moves from Stage A → B → C, you'll get three separate notifications.

  1. 1Click '+' after the Filter node
  2. 2Search 'Remove Duplicates' and select it
  3. 3Set Mode to 'Remove Items Seen in Previous Executions'
  4. 4Set the Deduplication Key to '={{ $json.id }}-{{ $json.stage_id }}'
What you should see: On the first run, all matching deals pass through. On the second run within the same stage, those same deal+stage combinations are blocked. You should see the 'Kept' count in the node output drop to 0 for unchanged deals.
Common mistake — n8n stores deduplication state per workflow. If you duplicate this workflow or reset execution history, all previously seen deal+stage pairs will fire again and flood Slack.
7

Canvas > + > HTTP Request

Look up pipeline stage name

Copper stores stage changes as a numeric stage_id, not a human-readable name. Add an HTTP Request node to fetch the pipeline stages from Copper's API. Call GET https://api.copper.com/developer_api/v1/pipelines and parse the response to find the stage name matching the deal's pipeline_id and stage_id. This lookup runs once per deal notification, so it adds one API call per triggered deal.

  1. 1Click '+' after the Remove Duplicates node
  2. 2Search 'HTTP Request' and select it
  3. 3Set Method to 'GET'
  4. 4Set URL to 'https://api.copper.com/developer_api/v1/pipelines'
  5. 5Under Headers, add 'X-PW-AccessToken' with your Copper API key
  6. 6Add header 'X-PW-UserEmail' with your Copper account email
  7. 7Add header 'Content-Type' as 'application/json'
What you should see: The HTTP Request output should return an array of pipeline objects, each containing a 'stages' array with id and name fields you can reference in the next step.
Common mistake — Copper requires three headers on every API request: X-PW-AccessToken, X-PW-UserEmail, and Content-Type. Missing any one of them returns a 401 even if your key is correct.
8

Canvas > + > Code

Extract and format the stage name with a Code node

Add a Code node after the HTTP Request to resolve the stage name from the pipeline response and build the Slack message payload. This is where n8n's code node earns its keep — you can do the lookup and message formatting in one step. Paste the JavaScript from the Pro Tip section below. The output will be a single object with all the fields your Slack message needs.

  1. 1Click '+' after the HTTP Request node
  2. 2Search 'Code' and select it
  3. 3Set Language to 'JavaScript'
  4. 4Paste the code from the Pro Tip section into the code editor
  5. 5Click 'Test Step' to verify the stage name resolves correctly
What you should see: The Code node output should show a single item containing fields: dealName, stageName, dealValue, ownerName, dealId, and slackMessage — all populated with real values from the current deal.
Common mistake — Map fields using the variable picker — don't type field names manually. Hand-typed variable names often have invisible spacing errors that produce blank output.

Paste this into the Code node (Step 8) between the HTTP Request and Slack nodes. It resolves the stage name from the pipeline API response, formats the deal value, picks an emoji based on stage name, and builds the complete Slack message string — all in one pass.

JavaScript — Code Node// Inputs:
▸ Show code
// Inputs:
// - $input: the current deal item from Remove Duplicates
// - $node['HTTP Request'].json: the pipeline data from Copper

... expand to see full code

// Inputs:
// - $input: the current deal item from Remove Duplicates
// - $node['HTTP Request'].json: the pipeline data from Copper

const deal = $input.first().json;
const pipelines = $node['HTTP Request'].json;

// Find the correct pipeline, then the correct stage within it
const pipeline = Array.isArray(pipelines)
  ? pipelines.find(p => p.id === deal.pipeline_id)
  : null;

const stage = pipeline
  ? pipeline.stages.find(s => s.id === deal.stage_id)
  : null;

const stageName = stage ? stage.name : 'Unknown Stage';

// Emoji map — add your own stage names here
const stageEmoji = {
  'Won': '🏆',
  'Lost': '❌',
  'Negotiation': '🔴',
  'Proposal Sent': '🔵',
  'Qualified': '🟡',
  'Discovery': '🟢',
};
const emoji = stageEmoji[stageName] || '📋';

// Format monetary value
const rawValue = deal.monetary_value;
const dealValue = rawValue != null
  ? `$${Number(rawValue).toLocaleString('en-US')}`
  : 'No value set';

// Format close date (Copper returns Unix seconds)
const closeDate = deal.close_date
  ? new Date(deal.close_date * 1000).toLocaleDateString('en-US', {
      month: 'short', day: 'numeric', year: 'numeric'
    })
  : 'No close date';

// Build deep link back to Copper deal
const dealUrl = `https://app.copper.com/companies/app#/deals/${deal.id}`;

// Owner name: Copper returns assignee_id (numeric).
// For a real name, you'd need a second API call to /people/{id}.
// As a placeholder, we flag it clearly:
const ownerLabel = deal.assignee ? deal.assignee.name : `Rep ID ${deal.assignee_id}`;

// Build Slack message
const slackMessage = [
  `${emoji} *${deal.name}* moved to *${stageName}*`,
  `💰 ${dealValue} · 👤 ${ownerLabel} · 📅 Close: ${closeDate}`,
  `🔗 View in Copper: ${dealUrl}`
].join('\n');

return [{
  json: {
    dealId: deal.id,
    dealName: deal.name,
    stageName,
    dealValue,
    ownerLabel,
    closeDate,
    slackMessage,
    dealUrl
  }
}];
9

Canvas > + > Slack > Message > Send

Connect your Slack credentials and configure the message

Add a Slack node after the Code node. Set Resource to 'Message' and Operation to 'Send'. Click 'Credential for Slack API' and create a new credential using your Slack Bot Token (starts with xoxb-). Set the Channel field to your target channel name, like #pipeline-updates. Set the Message Text field to '={{ $json.slackMessage }}' to pull in the formatted message from the Code node.

  1. 1Click '+' after the Code node
  2. 2Search 'Slack' and select the Slack node
  3. 3Set Resource to 'Message'
  4. 4Set Operation to 'Send'
  5. 5Click 'Credential for Slack API' and select or create your bot credential
  6. 6Set Channel to '#pipeline-updates' (or your channel name)
  7. 7Set Text to '={{ $json.slackMessage }}'
What you should see: After clicking 'Test Step', you should see a Slack message appear in your target channel within a few seconds and a green success response in the n8n output panel.
Common mistake — Your Slack bot must be invited to the target channel before it can post. If it isn't, the node will return a 'channel_not_found' or 'not_in_channel' error even though the bot token is valid.
10

Workflow Settings > Error Workflow

Add error handling with an Error Trigger

Add a separate Error Trigger workflow to catch failures. In your main workflow, click the three-dot menu in the top right and select 'Settings'. Enable 'Error Workflow' and point it to a separate n8n workflow that sends a Slack DM to the workflow owner if something breaks. Without this, polling failures are silent — you won't know notifications stopped until someone asks why Slack went quiet.

  1. 1Click the three-dot menu (⋮) in the top right of the workflow editor
  2. 2Click 'Settings'
  3. 3Toggle 'Error Workflow' to ON
  4. 4Select an existing error-handler workflow or click 'Create New'
  5. 5In the error workflow, add a Slack node that DMs the admin with '={{ $json.execution.error.message }}'
What you should see: Under Workflow Settings, you should see the Error Workflow field pointing to your chosen error handler workflow by name.
11

Workflow Editor > Active Toggle > Executions Tab

Activate and verify the workflow

Click the 'Inactive' toggle in the top right of the workflow editor to switch it to 'Active'. n8n will now run the Schedule Trigger every 3 minutes. To verify it's working, manually move a deal to a new stage in Copper, then wait up to 5 minutes and check your Slack channel. The notification should appear with the deal name, new stage, and owner. Check the Executions tab to see each run's output and confirm no errors.

  1. 1Click the 'Inactive' toggle in the top right — it should turn green and read 'Active'
  2. 2Open Copper and manually move a test deal to a different pipeline stage
  3. 3Wait 3–5 minutes
  4. 4Check your Slack channel for the notification
  5. 5Click 'Executions' in the left sidebar to review run logs
What you should see: The Executions tab should show a new execution every 3 minutes. The execution where your test deal was picked up should show data flowing through all nodes with green checkmarks.

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 you self-host your tools, want zero per-task pricing, or need the Code node to do non-trivial data shaping — like resolving Copper's numeric stage IDs to human-readable names in the same step that builds the Slack message. n8n also lets you version-control workflows as JSON, which matters if you're managing this alongside engineering infrastructure. The one case where you'd pick something else: if your team has zero technical capacity and you need this running in 20 minutes — Zapier's Copper integration has a pre-built 'Updated Deal' trigger that requires no code at all.

Cost

The cost math is straightforward. This workflow runs every 3 minutes — that's 480 executions per day, 14,400 per month. On n8n Cloud's Starter plan ($20/month), you get 2,500 executions. You'll need the Pro plan ($50/month) to cover 14,400 executions. Self-hosted n8n is free with no execution limits — the only cost is your server ($5–$10/month on a small VPS). Zapier at this volume would cost $49/month on the Professional plan. Make would handle this workflow in a Free tier scenario with careful scoping, but Make's Copper module has fewer filtering options, so you may burn more operations per run.

Tradeoffs

Zapier has one genuine advantage here: its Copper 'Updated Opportunity' trigger is event-driven within Zapier's poller — the UI is simpler and the setup takes half the time. Make handles conditional branching better visually when you want to route Won vs. Stalled deals to different channels — the Router module is cleaner than n8n's IF node for multi-path logic. Power Automate has no official Copper connector, so you'd be doing custom HTTP calls for every step — skip it. Pipedream's Copper polling source is well-maintained and supports filtering by stage change with less code. But n8n wins here for teams that want self-hosted control, need the Code node for stage name resolution, or are already running n8n for other workflows.

Three things you'll hit after going live. First, Copper's 'date_modified' field updates on any change — a note added by a rep, a tag change, or a linked email all trigger the poll filter. Your deduplication node handles this, but you'll see executions where items get filtered at the Remove Duplicates step more often than you expect. Second, the Copper /pipelines endpoint occasionally returns an empty stages array for newly created pipelines that have no deals yet — your Code node will output 'Unknown Stage' and the Slack message will look broken. Add a guard clause. Third, Slack's API throttles bots that post more than 1 message per second to the same channel. If a large batch of deals change stage simultaneously (end of quarter pipeline cleanup, for example), add a Wait node set to 1 second between Slack posts to avoid 429 rate limit errors.

Ideas for what to build next

  • Route 'Won' deals to a dedicated #wins channelAdd an IF node after the Code node that checks if stageName equals 'Won' and routes those to #sales-wins while all other stages go to #pipeline-updates. Takes about 10 minutes to add.
  • Fetch the owner's real name via Copper People APIThe current setup uses the assignee_id as a fallback label. Add a second HTTP Request node calling GET /v1/people/{assignee_id} to resolve it to a real name and display it in the Slack message.
  • Send a daily digest instead of per-deal messagesChange the Schedule Trigger to run once per day at 9am, collect all stage changes in the past 24 hours, and post a single formatted summary to Slack using an n8n Aggregate node before the Code node.

Related guides

Was this guide helpful?
Slack + Copper overviewn8n profile →