

How to Log Slack Messages to Copper CRM with n8n
Watches a Slack channel for customer messages and writes a timestamped activity note to the matching Copper contact record automatically.
Steps and UI details are based on platform versions at time of writing — check each platform for the latest interface.
Best for
Small sales or CS teams who run customer conversations in Slack and need those interactions visible inside Copper without manual copy-paste.
Not ideal for
Teams logging hundreds of messages per hour — Copper's API rate limit of 1 request/second will cause queuing problems at that volume.
Sync type
real-timeUse case type
syncReal-World Example
A 12-person SaaS company uses a dedicated #customer-success Slack channel where reps post updates after calls, demos, and check-ins. Before this workflow, those updates lived only in Slack — Copper showed no activity for weeks at a time, making it impossible to prep for renewal conversations. Now every message posted to the channel creates a Copper activity note on the matching contact within 30 seconds.
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 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.
Field Mapping
Map these fields between your apps.
| Field | API Name | |
|---|---|---|
| Required | ||
| Contact ID | parent_id | |
| Parent Type | parent_type | |
| Activity Type | type | |
| Activity Details | details | |
| Slack Message Text | ||
| Slack User Email | ||
| Message Timestamp | ||
2 optional fields▸ show
| Channel Name | |
| Sender Display Name |
Step-by-Step Setup
n8n Dashboard > Workflows > + New Workflow
Create a new n8n workflow
Log into your n8n instance and click the '+ New Workflow' button in the top-right corner of the Workflows dashboard. Give it a clear name like 'Slack → Copper: Customer Message Log'. You'll land on the canvas with an empty trigger node waiting to be configured. This name shows up in execution logs, so make it descriptive.
- 1Click '+ New Workflow' in the top-right
- 2Click the workflow title field and type 'Slack → Copper: Customer Message Log'
- 3Press Enter to save the name
Canvas > Add First Step > Search 'Slack' > Slack Trigger > Message Received
Add the Slack trigger node
Click the grey 'Add first step' node on the canvas. In the search bar that appears, type 'Slack' and select the Slack node from the results. Set the resource to 'Message' and the event to 'Message Received'. This uses Slack's Events API via webhook, which means n8n gets notified the moment a message is posted — no polling delay. You'll need a Slack app with the webhook URL configured in the next step.
- 1Click the grey 'Add first step' node
- 2Type 'Slack' in the search field
- 3Select 'Slack Trigger' from the list
- 4Set Event to 'Message Received'
- 5Click 'Add Credential' to connect your Slack app
api.slack.com/apps > [Your App] > Event Subscriptions > Subscribe to Bot Events
Configure the Slack app and subscribe to events
Open api.slack.com/apps, select your Slack app (or create one), and navigate to Event Subscriptions. Toggle 'Enable Events' to On, then paste the n8n webhook URL into the Request URL field. Slack will immediately send a challenge request — n8n auto-responds to verify the URL. Under 'Subscribe to bot events', add the 'message.channels' scope so the app receives messages from public channels. Save the changes and reinstall the app to your workspace.
- 1Go to api.slack.com/apps and select your app
- 2Click 'Event Subscriptions' in the left sidebar
- 3Toggle 'Enable Events' to On
- 4Paste the n8n webhook URL into the 'Request URL' field
- 5Click '+ Add Bot User Event' and add 'message.channels'
- 6Click 'Save Changes', then reinstall the app when prompted
Canvas > + Node > IF > Conditions
Filter messages to the correct Slack channel
Add an IF node after the Slack trigger. You only want to log messages from your dedicated customer channel — not every channel in the workspace. Set the condition to check that the 'channel' field from the Slack output equals the channel ID of your target channel (e.g. C04ABCD1234). Channel IDs are found by right-clicking the channel name in Slack > View channel details > scroll to the bottom. Wire the 'true' branch to the next step; leave the 'false' branch empty.
- 1Click the '+' button to the right of the Slack Trigger node
- 2Search for and select 'IF'
- 3Set Condition 1 Value 1 to '{{ $json.channel }}'
- 4Set the operator to 'Equal'
- 5Set Value 2 to your channel ID (e.g. C04ABCD1234)
Canvas > + Node > HTTP Request
Extract the sender's email via Slack API
Copper matches contacts by email address. The Slack message payload gives you a user ID (like U05XYZ789), not an email. Add an HTTP Request node to call the Slack users.info API and resolve the user ID to a profile including their email. Set the method to GET, URL to 'https://slack.com/api/users.info', and add a query parameter 'user' set to '{{ $json.user }}'. Add the Authorization header with your Slack bot token as 'Bearer xoxb-...'.
- 1Add an HTTP Request node after the IF node's 'true' branch
- 2Set Method to 'GET'
- 3Set URL to 'https://slack.com/api/users.info'
- 4Under Query Parameters, add 'user' = '{{ $json.user }}'
- 5Under Headers, add 'Authorization' = 'Bearer xoxb-your-bot-token'
Canvas > + Node > Copper > Person > Search
Look up the matching Copper contact by email
Add a Copper node and set the resource to 'Person' and operation to 'Search'. Use the email resolved in step 5 as the search term: '{{ $json.user.profile.email }}'. Copper's search returns an array — if it returns an empty array, the contact doesn't exist yet and you'll need to decide whether to create a new one or skip. For now, wire a second IF node to check that the result array length is greater than 0.
- 1Add a Copper node after the HTTP Request node
- 2Set Resource to 'Person'
- 3Set Operation to 'Search'
- 4Set the 'Email' field to '{{ $json.user.profile.email }}'
- 5Click 'Execute Node' to test with a real email
Canvas > + Node > IF
Guard against missing contacts
Add an IF node after the Copper search. Set the condition to check that '{{ $json.length }}' is greater than 0, or more reliably, check that '{{ $node["Copper"].json[0].id }}' exists. Wire the 'true' branch to the activity creation step. Wire the 'false' branch to a No-Op or an optional Slack notification back to the team that the contact wasn't found. This prevents failed Copper API calls from cluttering your execution log.
- 1Add an IF node after the Copper Search node
- 2Set Value 1 to '{{ $node["Copper Search"].json[0].id }}'
- 3Set operator to 'Is Not Empty'
- 4Leave the 'false' branch disconnected or connect a Slack message node to alert the team
Canvas > + Node > Code
Build the activity note text
Add a Code node (JavaScript) to format the Slack message into a clean Copper activity note. This is where you combine the message text, timestamp, and channel context into a single readable string. Copper activity notes are plain text — there's no markdown rendering. Keep the format structured so it's scannable inside Copper's activity feed. Paste the code from the pro tip section below into this node.
- 1Click '+' after the IF node's 'true' branch
- 2Search for and select 'Code'
- 3Set Language to 'JavaScript'
- 4Paste the pro tip code into the editor
- 5Click 'Execute Node' to preview the formatted output
Canvas > + Node > Copper > Activity > Create
Create the Copper activity note
Add a second Copper node and set the resource to 'Activity' and the operation to 'Create'. Set the 'Parent Type' to 'person' and 'Parent ID' to '{{ $node["Copper Search"].json[0].id }}'. Set 'Type' to 'note' and 'Details' to the formatted note text from the Code node: '{{ $json.noteText }}'. The activity will appear in the contact's timeline inside Copper immediately after the API call succeeds.
- 1Add a Copper node after the Code node
- 2Set Resource to 'Activity'
- 3Set Operation to 'Create'
- 4Set 'Parent Type' to 'person'
- 5Set 'Parent ID' to '{{ $node["Copper Search"].json[0].id }}'
- 6Set 'Details' to '{{ $json.noteText }}'
n8n Canvas > Toggle Workflow Active > Executions (left sidebar)
Test end-to-end with a real Slack message
Activate the workflow using the toggle in the top-right of the canvas. Go to the configured Slack channel and post a test message. Switch back to n8n and open the Executions panel from the left sidebar. You should see a new execution appear within 5-10 seconds. Click it to inspect each node's input and output. Then open Copper, find the contact whose email matches the Slack user, and confirm the activity note appears in their timeline.
- 1Click the 'Inactive' toggle in the top-right to activate the workflow
- 2Post a test message in the configured Slack channel
- 3Click 'Executions' in the left sidebar
- 4Click the latest execution to inspect node outputs
- 5Open Copper and navigate to the contact's Activity tab to verify the note
This Code node runs between the Copper contact lookup and the Copper activity creation step. It formats the raw Slack data into a clean, timestamped note string and handles the Unix-to-readable timestamp conversion. Paste it into a Code node (JavaScript mode) immediately after the contact guard IF node.
JavaScript — Code Node// n8n Code Node — Format Slack message into Copper activity note▸ Show code
// n8n Code Node — Format Slack message into Copper activity note // Place this node between the contact lookup IF and the Copper Activity Create node const items = $input.all();
... expand to see full code
// n8n Code Node — Format Slack message into Copper activity note
// Place this node between the contact lookup IF and the Copper Activity Create node
const items = $input.all();
const results = [];
for (const item of items) {
// Slack timestamp is Unix seconds with decimal — convert to milliseconds
const slackTs = item.json.ts || '';
const tsMillis = parseFloat(slackTs) * 1000;
const messageDate = new Date(tsMillis);
// Format as readable UTC datetime string
const formattedDate = messageDate.toISOString()
.replace('T', ' ')
.substring(0, 16) + ' UTC';
// Pull sender display name from the upstream users.info HTTP node
const senderName =
$node['Get Slack User'].json?.user?.profile?.real_name ||
$node['Get Slack User'].json?.user?.name ||
'Unknown Sender';
// Pull channel name — hardcode or resolve via conversations.info
const channelLabel = '#customer-success';
// Raw message text — strip Slack user mention formatting like <@U05XYZ789>
const rawText = item.json.text || '';
const cleanText = rawText.replace(/<@[A-Z0-9]+>/g, '').trim();
// Compose the final note body
const noteText = `[Slack | ${channelLabel} | ${formattedDate}] ${senderName}: ${cleanText}`;
results.push({
json: {
noteText,
formattedDate,
senderName,
cleanText,
originalTs: slackTs
}
});
}
return results;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 n8n for this if you self-host, want full control over the code that formats notes, and need the users.info API call to resolve Slack IDs to emails without paying per task. The Code node here is the real reason n8n wins: you're doing a Unix timestamp conversion, stripping Slack mention syntax, and composing a formatted string — that's three operations that would cost you three separate steps in Zapier. The one scenario where you'd pick something else: if your team is non-technical and needs to maintain this workflow themselves, Make's visual interface is significantly easier to hand off.
n8n's self-hosted version costs you server time only — a $6/month DigitalOcean droplet handles this easily. Each Slack message triggers one execution with three API calls: Slack users.info, Copper search, and Copper activity create. At 200 customer messages per month, that's 200 executions and roughly $0 in platform cost. n8n Cloud starts at $20/month for 2,500 executions — at 200 messages/month you're well inside that. Zapier would charge you for each of those three Zap steps separately; at 200 messages that's 600 task uses, which exhausts the free tier (100 tasks) in days and pushes you to the $19.99/month Starter plan fast.
Make handles the timestamp formatting and text manipulation in a single module using its built-in text functions — no Code module needed, which makes it slightly easier for non-developers. Zapier has a 'Formatter' step that can do the timestamp conversion, but you'd need a separate Code by Zapier step for the mention-stripping regex, and each step costs a task. Power Automate has native connectors for neither Slack events (you'd use a webhook trigger manually) nor Copper (HTTP actions only), making the setup significantly more painful. Pipedream's code environment is as capable as n8n's and handles the Slack OAuth token management more gracefully, but it's cloud-only, which matters if you have data residency requirements. n8n is still the right call here because the self-hosted option, combined with the Code node, gives you the most control at the lowest recurring cost.
Three things you'll hit after setup. First, Copper's search API does a substring match, not an exact match — searching for '[email protected]' might return contacts with similar emails if your data is messy. Always check the returned array length and validate the email field of result[0] before using the ID. Second, Slack timestamps have microsecond precision and come as strings like '1710509520.123456' — parseFloat() handles this, but if you try parseInt() you'll cut off the decimal and the Date object will be off by a fraction of a second, which causes subtle ordering issues in Copper's activity feed. Third, the Slack Events API will retry delivery up to 3 times if your n8n instance doesn't respond with a 200 within 3 seconds. If your Copper API call is slow (it can hit 800ms), you may get duplicate activity notes. Add a deduplication check using the Slack message 'ts' field stored in a simple n8n variable or a lightweight external key-value store.
Ideas for what to build next
- →Log to Copper Opportunity Instead of Contact — If conversations are deal-specific, modify the Copper lookup to search Opportunities by the contact's associated deals and attach the note there instead. This keeps activity tied to revenue context.
- →Add a Daily Digest Summary — Build a second n8n workflow on a Schedule trigger that pulls all Copper activities created in the last 24 hours and posts a summary to a Slack channel — gives the team visibility without checking Copper directly.
- →Create Contact if Not Found in Copper — Wire the 'false' branch of the contact guard IF node to a Copper 'Create Person' node that creates a new contact using the email and display name from Slack, then continues to log the activity on the new record.
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