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

How to Send Copper Deal Stage Alerts to Slack with Power Automate

Polls Copper for deal stage changes and posts a formatted Slack channel message with the deal name, new stage, owner, and value every time an opportunity moves forward or stalls.

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

Best for

Microsoft-shop sales teams already using Power Automate who want deal stage visibility in Slack without leaving their existing automation stack.

Not ideal for

Teams that need sub-minute alerts — Power Automate polls Copper on a schedule, so you'll see up to a 15-minute lag between a stage change and the Slack message.

Sync type

scheduled

Use case type

notification

Real-World Example

💡

A 12-person B2B software sales team routes all deal alerts to a #pipeline Slack channel so reps and their manager see stage changes without logging into Copper. Before this flow, the sales manager exported a pipeline report each morning and pinged reps manually when deals hadn't moved — a 20-minute daily task that still missed same-day changes. After setup, every stage move posts automatically within 15 minutes, and the team added a 🏆 emoji rule for 'Won' stage posts that turned into a team ritual.

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 Power Automate

Copy the pre-built Power Automate blueprint and paste it straight into Power Automate. 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: Generate one in Copper under Settings > Integrations > API Keys. You need an account with admin or API access permissions.
Copper user account with at least 'Read' access to Opportunities so the search endpoint returns pipeline data.
Slack bot installed in your workspace with 'chat:write' scope enabled, and the bot invited to the target channel before the flow runs.
Power Automate account with a license that supports HTTP connector actions — this requires a paid Power Automate per-user or per-flow plan, not the free tier.

Optional

A sample Copper opportunity JSON response saved locally so you can generate the Parse JSON schema without running the flow blind.

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Deal Namename
Stage IDstage_id
Stage Namename
Date Modifieddate_modified
Deal IDid
4 optional fields▸ show
Monetary Valuemonetary_value
Assignee IDassignee_id
Close Dateclose_date
Pipeline IDpipeline_id

Step-by-Step Setup

1

make.powerautomate.com > My flows > + New flow > Scheduled cloud flow

Create a new Scheduled cloud flow

Go to make.powerautomate.com and sign in. From the left sidebar, click 'My flows', then click '+ New flow' in the top bar and select 'Scheduled cloud flow'. Name the flow 'Copper Deal Stage Notifications'. Set the recurrence to every 15 minutes — this is the polling interval that determines how quickly stage changes reach Slack. Click 'Create' to open the flow editor.

  1. 1Click 'My flows' in the left sidebar
  2. 2Click '+ New flow' in the top toolbar
  3. 3Select 'Scheduled cloud flow' from the dropdown
  4. 4Enter 'Copper Deal Stage Notifications' in the Name field
  5. 5Set Repeat every to '15' and select 'Minute' from the unit dropdown
  6. 6Click 'Create'
What you should see: The flow editor opens with a single 'Recurrence' trigger tile showing 'Every 15 Minute(s)'.
Common mistake — Do not set the interval below 5 minutes. Copper's API rate limit is 1,000 requests per minute per account, but Power Automate's own polling flows add overhead actions that compound quickly if you run multiple flows simultaneously.
2

Flow editor > + New step > Variables > Initialize variable

Initialize a variable to store the last-checked timestamp

Click '+ New step' below the Recurrence trigger. Search for 'Initialize variable' and select the 'Variables' action. Name the variable 'LastRunTime' and set the type to 'String'. In the Value field, paste this expression: addMinutes(utcNow(), -15) — this tells the flow to look back 15 minutes on each run, matching your poll interval. This variable is how you avoid re-processing deals that already sent a notification.

  1. 1Click '+ New step'
  2. 2Type 'Initialize variable' in the search bar
  3. 3Select 'Initialize variable' under the Variables connector
  4. 4Set Name to 'LastRunTime'
  5. 5Set Type to 'String'
  6. 6Click inside the Value field, then click 'Expression' tab and paste: addMinutes(utcNow(), -15)
  7. 7Click 'OK'
What you should see: The Initialize variable tile shows Name: LastRunTime, Type: String, Value: addMinutes(utcNow(), -15).
Common mistake — Using a static timestamp here is intentional for simplicity, but it means if your flow run is delayed or fails, you could miss deals that changed during the gap. For production use, store the last successful run time in a SharePoint list or Azure Table and read it at the start of each run instead.
3

Flow editor > + New step > HTTP

Connect to Copper via HTTP to fetch recently updated opportunities

Power Automate does not have a native Copper connector, so you'll use the HTTP action to call Copper's REST API directly. Click '+ New step', search for 'HTTP', and select the HTTP action (not HTTP + Swagger). Set Method to POST and URI to https://api.copper.com/developer_api/v1/opportunities/search. Add three headers: X-PW-AccessToken with your Copper API key, X-PW-Application set to developer_api, and Content-Type set to application/json. In the Body field, enter the JSON payload shown in the sub-steps below.

  1. 1Click '+ New step'
  2. 2Type 'HTTP' in the search bar and select the HTTP action
  3. 3Set Method to 'POST'
  4. 4Set URI to: https://api.copper.com/developer_api/v1/opportunities/search
  5. 5Click '+ Add new parameter' and add Header: X-PW-AccessToken = [your Copper API key]
  6. 6Add Header: X-PW-Application = developer_api
  7. 7Add Header: Content-Type = application/json
  8. 8In Body, paste: {"page_size": 200, "sort_by": "date_modified", "sort_direction": "desc"}
What you should see: The HTTP tile shows a POST to the Copper search endpoint with all three headers and a body containing the pagination and sort parameters.
Common mistake — Copper requires all three headers on every request — missing X-PW-Application alone returns a 401 even if your API key is correct. Double-check the header name spelling; it is case-sensitive.
4

Flow editor > + New step > Data Operation > Parse JSON

Parse the Copper API response

After the HTTP action, click '+ New step' and search for 'Parse JSON'. Select it from the Data Operation connector. In the Content field, click the dynamic content picker and select 'Body' from the HTTP step. For the Schema field, click 'Generate from sample' and paste in a sample Copper opportunity JSON response (see the schema in the pro tip section). This step converts the raw API response into named fields that you can reference in later steps without writing expressions.

  1. 1Click '+ New step'
  2. 2Type 'Parse JSON' in the search bar
  3. 3Select 'Parse JSON' under Data Operation
  4. 4Click the Content field and select 'Body' from the HTTP step in dynamic content
  5. 5Click 'Generate from sample'
  6. 6Paste your sample Copper opportunity array JSON and click 'Done'
What you should see: The Parse JSON tile shows a populated schema with fields like id, name, stage_id, monetary_value, and assignee_id visible in the schema editor.
5

Flow editor > + New step > Data Operation > Filter array

Apply a filter to isolate deals modified in the last 15 minutes

Click '+ New step' and search for 'Filter array' under Data Operation. Set the From field to the array output of your Parse JSON step. In the filter condition, set the left side to the date_modified field from dynamic content, set the operator to 'is greater than or equal to', and set the right side to the expression variables('LastRunTime'). This narrows the full opportunity list down to only deals that changed since the last poll run.

  1. 1Click '+ New step'
  2. 2Search for 'Filter array' and select it under Data Operation
  3. 3Set the From field to the body/array output of the Parse JSON step
  4. 4Click 'Edit in advanced mode' if simple mode fields are not flexible enough
  5. 5In the left condition field, select 'date_modified' from dynamic content
  6. 6Set operator to 'is greater than or equal to'
  7. 7In the right field, switch to Expression and type: variables('LastRunTime')
What you should see: The Filter array tile shows the From source pointing to the Parse JSON output and a condition comparing date_modified to LastRunTime.
Common mistake — Copper returns date_modified as a Unix timestamp (integer seconds), not an ISO string. The LastRunTime expression also returns an ISO string. You must convert both to the same format — see the pro tip section for the expression that handles this conversion.
Slack
SL
trigger
filter
Deal Stage
matches criteria?
yes — passes through
no — skipped
Copper
CO
notified
6

Flow editor > + New step > Control > Apply to each

Add an Apply to each loop over filtered deals

Click '+ New step' and search for 'Apply to each'. Set the output from previous steps field to the Body output of the Filter array step. Every action you add inside this loop runs once per deal that changed stage. This is where you'll build the Slack message. Keep the loop as lean as possible — don't add extra HTTP calls inside it unless necessary, since each one counts against your Copper rate limit.

  1. 1Click '+ New step'
  2. 2Select 'Control' then 'Apply to each'
  3. 3Click the 'Select an output from previous steps' field
  4. 4Choose 'Body' from the Filter array step in dynamic content
  5. 5Click inside the loop to start adding actions
What you should see: An 'Apply to each' loop tile appears with a dashed border. The 'Select an output' field shows the Filter array Body. The loop is empty, waiting for actions.
Common mistake — Filters are the most common place setups break. Double-check the field name and value exactly match what your app sends — a single capital letter difference will block everything.
7

Flow editor > Apply to each > + Add an action > HTTP

Resolve the pipeline stage name from the stage ID

Copper passes stage_id as a numeric ID, not a human-readable label like 'Proposal Sent'. Inside the Apply to each loop, add another HTTP action that calls GET https://api.copper.com/developer_api/v1/pipeline_stages/[stage_id] using the same three Copper headers. Replace [stage_id] with the dynamic content field stage_id from your Parse JSON output. Follow this with a second Parse JSON step to extract the name field from the stage response. This gives you the readable stage name to display in Slack.

  1. 1Inside the Apply to each loop, click '+ Add an action'
  2. 2Search for 'HTTP' and select it
  3. 3Set Method to 'GET'
  4. 4Set URI to: https://api.copper.com/developer_api/v1/pipeline_stages/ then append the stage_id dynamic content field
  5. 5Add the same three Copper headers: X-PW-AccessToken, X-PW-Application, Content-Type
  6. 6Add a second Parse JSON action inside the loop after this HTTP call
  7. 7Set Content to the Body of this HTTP call and generate schema from a sample stage response
What you should see: Inside the loop you now have two HTTP actions and two Parse JSON actions. The second Parse JSON exposes a 'name' field containing the stage label like 'Proposal Sent'.
Common mistake — Each iteration of the loop makes an extra API call to resolve the stage name. If 50 deals update at once, that's 50 additional Copper API calls in one flow run. For teams with high deal velocity, cache stage names in a SharePoint list and do a lookup instead of an API call.

This expression block goes inside a Compose action placed between your Filter array step and the Apply to each loop. It handles three things at once: converts the Copper Unix timestamp to a readable date, formats monetary_value as USD currency, and builds the full Slack message string so the Slack action only needs to reference a single Compose output. Paste the expression into the Compose action's Inputs field using the Expression editor tab.

JavaScript — Code Step// Place this in a Compose action (Expression mode) BEFORE the Apply to each loop.
▸ Show code
// Place this in a Compose action (Expression mode) BEFORE the Apply to each loop.
// Reference: outputs('Compose_Deal_Message') in the Slack Message Text field.
// Full message expression (paste as one block in the Compose Inputs field):

... expand to see full code

// Place this in a Compose action (Expression mode) BEFORE the Apply to each loop.
// Reference: outputs('Compose_Deal_Message') in the Slack Message Text field.

// Full message expression (paste as one block in the Compose Inputs field):
concat(
  if(
    equals(string(item()?['stage_id']), '7'),
    '🏆 *Deal Won:* ',
    '*Deal Update:* '
  ),
  item()?['name'],
  ' moved to *',
  body('Parse_Stage_Response')?['name'],
  '*. Value: ',
  formatNumber(item()?['monetary_value'], 'C0', 'en-US'),
  '. Close Date: ',
  formatDateTime(
    addSeconds('1970-01-01T00:00:00Z', item()?['close_date']),
    'MMM d, yyyy'
  ),
  '. View: https://app.copper.com/companies/',
  string(item()?['id'])
)
8

Flow editor > Apply to each > + Add an action > Slack > Post message

Add the Slack connection and configure the message action

Inside the Apply to each loop, click '+ Add an action' and search for 'Slack'. Select 'Post message' from the Slack connector. If you haven't connected Slack yet, Power Automate will prompt you to sign in — click 'Sign in' and authorize the bot with the correct workspace. In the Channel Name field, type the exact Slack channel name without the # symbol (e.g. pipeline or deals). Build the message text using dynamic content fields from your Parse JSON steps.

  1. 1Inside the Apply to each loop, click '+ Add an action'
  2. 2Type 'Slack' in the search bar
  3. 3Select 'Post message' from the Slack connector
  4. 4If prompted, click 'Sign in' and complete the OAuth flow for your Slack workspace
  5. 5In the Channel Name field, type the target channel name (no # prefix)
  6. 6In the Message Text field, compose your message using dynamic content
What you should see: The Slack tile shows your workspace name in the connection field, the channel name populated, and a message body with dynamic content tokens visible as colored chips.
Common mistake — The Power Automate Slack connector uses a bot token, not a user token. The bot must be invited to the channel before the flow runs — if it isn't, the action fails silently with a 'channel_not_found' error and Power Automate logs it as a succeeded action.
message template
🔔 New Record: {{text}} {{user}}
channel: {{channel}}
ts: {{ts}}
#sales
🔔 New Record: Jane Smith
Company: Acme Corp
9

Flow editor > Apply to each > Slack > Post message > Message Text

Build the Slack message body with deal details

In the Message Text field of the Slack action, compose a message that includes the key deal fields. Use plain text with Slack mrkdwn formatting for readability. Reference dynamic content from your Parse JSON steps for each field. A good starting template: *Deal Update:* [name] moved to *[stage name]*. Value: $[monetary_value]. Owner: [assignee email]. View in Copper: https://app.copper.com/companies/[id]. Add a conditional prefix like 🏆 for 'Won' stage using a Condition action before the Slack step if you want emoji-based routing.

  1. 1Click inside the Message Text field of the Slack action
  2. 2Type '*Deal Update:* ' then select 'name' from dynamic content
  3. 3Type ' moved to *' then select the stage 'name' from the second Parse JSON output
  4. 4Type '*. Value: $' then select 'monetary_value' from dynamic content
  5. 5Type '. Owner: ' then select 'assignee_email' or resolve from assignee ID
  6. 6Type a newline then type the Copper deal URL with the dynamic 'id' appended
What you should see: The Message Text field shows a mix of static text and colored dynamic content chips. When you hover over a chip, it shows the source step and field name.
Common mistake — monetary_value comes back as a float without currency formatting. The raw value 75000.0 posts to Slack as '75000.0' not '$75,000'. Use the expression formatNumber(item()?['monetary_value'], 'C0', 'en-US') to get clean currency output.
10

Flow editor > Save > Test > Manually > Run flow

Test the flow with a live Copper deal

Click 'Save' in the top toolbar, then click 'Test' in the top right and select 'Manually'. Click 'Run flow'. Power Automate runs the flow immediately regardless of the 15-minute schedule. While the test runs, open Copper and manually move a deal to a different stage. If the deal's date_modified falls within the last 15 minutes, it will appear in the filtered results and trigger a Slack post. Watch the flow run details — each step shows a green checkmark or red X with the input/output payload visible.

  1. 1Click 'Save' in the top toolbar
  2. 2Click 'Test' in the upper right corner
  3. 3Select 'Manually' and click 'Run flow'
  4. 4Open Copper and move an existing deal to a different pipeline stage
  5. 5Return to Power Automate and watch the run details panel
  6. 6Click each step tile to expand the input/output JSON and verify field values
What you should see: Each step shows a green checkmark. The Apply to each loop shows '1 iteration' (or more if multiple deals changed). A Slack message appears in your target channel within 60 seconds of the manual run.
Power Automate
▶ Test flow
executed
Slack
Copper
Copper
🔔 notification
received
11

make.powerautomate.com > My flows > [Flow name] > Run history

Enable the flow and confirm the schedule is active

After a successful test, click the back arrow to return to the flow detail page. Confirm the flow status shows 'On' — it should be on by default after saving. Check the 'Run history' tab to see the first scheduled run after your test. The Recurrence trigger fires every 15 minutes from the time you created the flow. If you need to change the start time, click the Recurrence trigger tile and set the 'Start time' field to a specific UTC datetime.

  1. 1Click the back arrow to exit the flow editor
  2. 2Confirm the flow status indicator shows 'On'
  3. 3Click the 'Run history' tab
  4. 4Wait up to 15 minutes for the first scheduled run to appear
  5. 5Click any run entry to see per-step pass/fail and execution time
What you should see: The Run history tab shows at least one successful scheduled run. Each run entry displays a duration (typically 5-20 seconds) and a green 'Succeeded' status.
Common mistake — Confirm your workflow timezone matches your business timezone — n8n uses the instance timezone by default. Also verify the workflow is saved and set to Active, since Schedule Triggers won't fire on inactive workflows.

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 Power Automate for this if your team already lives in the Microsoft ecosystem — SharePoint, Teams, Azure AD — and you need deal notifications to fit into existing governance around connectors and data policies. The HTTP connector gives you full Copper API access without waiting for a native connector, and the expression language handles the Unix timestamp conversion and currency formatting that Copper's raw API output requires. The one case where you'd pick something else: if your team has zero tolerance for up-to-15-minute lag and needs sub-minute alerts, Power Automate's polling architecture can't match Make or n8n running a webhook listener.

Cost

The cost math is straightforward. This flow uses roughly 6-8 actions per run: Recurrence, Initialize Variable, HTTP (Copper search), Parse JSON, Filter array, Apply to each, HTTP (stage lookup), Slack post. At a 15-minute polling interval, that's 96 runs per day and roughly 700 base actions daily before any deals trigger the loop. Power Automate's per-user plan costs $15/month and includes 40,000 actions/month — you'll consume about 21,000 on polling overhead alone, leaving 19,000 for actual deal processing. That covers roughly 270 deal stage changes per month before you hit the cap. Make's equivalent setup costs $9/month with 10,000 operations and a native Copper module, making it cheaper for low-volume teams. For high volume (500+ stage changes/month), Zapier gets expensive fast at $49-$69/month for the tiers that handle that throughput.

Tradeoffs

Make has a native Copper module — 'Watch Opportunities' — that uses webhook-style instant triggers instead of polling, so stage changes hit Slack in under 30 seconds. Zapier's Copper integration is mature and requires no HTTP actions, but the Zap editor gives you less control over timestamp formatting and conditional routing. n8n lets you self-host and run the Copper HTTP calls in a Function node with full JavaScript, which is better for complex message formatting and deduplication logic. Pipedream's event sources can poll Copper and emit individual events per deal, which makes the downstream logic cleaner. Power Automate wins here specifically if: you need Azure Key Vault for API key storage, your IT team restricts which SaaS tools can hold credentials, or you're bundling this with other Microsoft 365 automations and want a single admin pane.

Three things you'll hit after setup. First, Copper's search endpoint returns all opportunities sorted by date_modified, but 'modified' means any field — custom fields, notes, tags. You will get Slack noise from deal updates that had nothing to do with stage. Fix this by storing the last known stage_id per deal in SharePoint and diffing on each run. Second, the Power Automate Slack connector posts as the bot user, and if your Slack workspace has a message retention policy or compliance archiving, those bot messages are treated differently than user messages — confirm with your Slack admin before rolling this out. Third, Copper's API returns assignee_id as a numeric integer, not a name or email. Resolving it requires a separate GET to /v1/users/[id], which adds another API call per loop iteration. Cache user IDs in a SharePoint list on first run and refresh weekly, or your Slack messages will either show raw IDs or burn through extra API calls on every deal update.

Ideas for what to build next

  • Add a daily digest instead of per-deal pingsSwitch to a Scheduled flow that runs once at 9am and posts a bulleted summary of all deals that changed stages in the past 24 hours. This reduces Slack noise for high-velocity teams while keeping visibility intact.
  • Route 'Won' and 'Lost' deals to separate Slack channelsAdd a Switch control inside the Apply to each loop that checks the stage name and posts Won deals to #wins and Lost deals to #retrospectives. Keeps celebration and analysis in the right spaces without manual filtering.
  • Write stage changes back to a SharePoint tracking listAdd a SharePoint 'Create item' action after each Slack post to log the deal name, stage, value, and timestamp. After 30 days, you have a stage-change history you can analyze in Power BI to find where deals stall most often.

Related guides

Was this guide helpful?
Slack + Copper overviewPower Automate profile →