

How to Convert Slack Messages to Asana Tasks with n8n
When a team member reacts to a Slack message with a specific emoji (e.g. ✅), n8n catches the event via webhook and creates a matching Asana task with the message text, sender name, and channel link attached.
Steps and UI details are based on platform versions at time of writing — check each platform for the latest interface.
Best for
Engineering or ops teams who triage action items inside Slack and need them tracked in Asana without leaving their conversation.
Not ideal for
Teams already using Asana's native Slack integration — it handles basic task creation without any setup cost.
Sync type
real-timeUse case type
routingReal-World Example
A 12-person product team at a B2B SaaS company uses this workflow to capture tasks that surface during daily standups in Slack. Before automation, a designated person copy-pasted action items into Asana after every call — about 20 minutes of manual work per day. Now, anyone adds a ✅ reaction and the task appears in Asana within 5 seconds, assigned to whoever sent the original message.
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 | ||
| Task Name | name | |
| Project GID | projects | |
6 optional fields▸ show
| Task Notes | notes |
| Due Date | due_on |
| Assignee GID | assignee |
| Slack Message Timestamp | |
| Slack Channel ID | |
| Sender Display Name |
Step-by-Step Setup
api.slack.com/apps > Create New App > From scratch
Create a Slack App with Event Subscriptions
Go to api.slack.com/apps and click 'Create New App', choosing 'From scratch'. Name it something like 'n8n Task Bot' and select your workspace. You need a custom Slack app because n8n's webhook listener requires a URL that Slack pushes events to — OAuth apps don't expose raw event data the same way. This step takes about 4 minutes.
- 1Go to api.slack.com/apps and click the green 'Create New App' button
- 2Select 'From scratch'
- 3Enter app name (e.g. 'n8n Task Bot') and select your Slack workspace
- 4Click 'Create App'
This Code node runs after the Webhook node and before the Asana node. It parses the raw Slack payload, builds the message permalink (by stripping the period from the ts value), truncates long messages to a clean task title, and outputs a structured object that later nodes can reference cleanly without complex nested expressions. Paste it as a 'Code' node between your IF node and the Asana node.
JavaScript — Code Node// n8n Code Node: Transform Slack reaction payload into clean Asana task fields▸ Show code
// n8n Code Node: Transform Slack reaction payload into clean Asana task fields // Place this node after the IF (emoji filter) node and before the Asana node // Expects: Webhook node named 'Webhook', conversations.history node named 'Fetch Message', users.info node named 'Get User'
... expand to see full code
// n8n Code Node: Transform Slack reaction payload into clean Asana task fields
// Place this node after the IF (emoji filter) node and before the Asana node
// Expects: Webhook node named 'Webhook', conversations.history node named 'Fetch Message', users.info node named 'Get User'
const webhookData = $node['Webhook'].json;
const messageData = $node['Fetch Message'].json;
const userData = $node['Get User'].json;
// Extract core fields
const channelId = webhookData.event.item.channel;
const rawTs = webhookData.event.item.ts;
const senderName = userData.user?.real_name || userData.user?.name || 'Unknown';
const messageText = messageData.messages?.[0]?.text || '(no message text found)';
// Build Slack permalink — remove the period from ts to match Slack URL format
const tsForUrl = rawTs.replace('.', '');
const slackWorkspace = 'acme'; // <-- replace with your Slack workspace subdomain
const permalink = `https://${slackWorkspace}.slack.com/archives/${channelId}/p${tsForUrl}`;
// Truncate message text for task name (Asana task names over 256 chars get cut off)
const MAX_TITLE_LENGTH = 200;
const taskName = messageText.length > MAX_TITLE_LENGTH
? messageText.substring(0, MAX_TITLE_LENGTH) + '…'
: messageText;
// Format notes field with context
const taskNotes = [
`Created from Slack message by ${senderName}`,
`Original message: ${permalink}`,
`Channel ID: ${channelId}`,
`Timestamp: ${new Date(parseFloat(rawTs) * 1000).toISOString()}`
].join('\n');
// Return clean object for downstream nodes
return [{
json: {
taskName,
taskNotes,
permalink,
senderName,
channelId,
rawTs,
messageText
}
}];n8n > New Workflow > + Add Node > Webhook
Set Up the n8n Webhook Node
Open n8n and create a new workflow. Add a 'Webhook' node as the first node — this is under the 'Trigger' category. Set the HTTP method to POST and note the generated webhook URL (it looks like https://your-n8n-instance.com/webhook/abc123). Copy this URL — you'll paste it into Slack's Event Subscriptions in the next step. Leave the workflow in 'Test' mode for now so you can verify events arrive correctly.
- 1Click '+ Add Node' in the center canvas
- 2Search for 'Webhook' and select it
- 3Set HTTP Method to 'POST'
- 4Click 'Copy URL' next to the test webhook URL
- 5Click 'Execute Node' to put it in listening mode
api.slack.com/apps > [Your App] > Event Subscriptions
Enable Event Subscriptions in Slack
Back in your Slack app settings, click 'Event Subscriptions' in the left sidebar and toggle 'Enable Events' to On. Paste the n8n test webhook URL into the 'Request URL' field. Slack immediately sends a challenge request to verify the URL — n8n's Webhook node handles this automatically and you'll see a green 'Verified' checkmark within a few seconds if n8n is in listening mode.
- 1Click 'Event Subscriptions' in the left sidebar
- 2Toggle 'Enable Events' to On
- 3Paste your n8n test webhook URL into the 'Request URL' field
- 4Wait for the green 'Verified' checkmark to appear
This Code node runs after the Webhook node and before the Asana node. It parses the raw Slack payload, builds the message permalink (by stripping the period from the ts value), truncates long messages to a clean task title, and outputs a structured object that later nodes can reference cleanly without complex nested expressions. Paste it as a 'Code' node between your IF node and the Asana node.
JavaScript — Code Node// n8n Code Node: Transform Slack reaction payload into clean Asana task fields▸ Show code
// n8n Code Node: Transform Slack reaction payload into clean Asana task fields // Place this node after the IF (emoji filter) node and before the Asana node // Expects: Webhook node named 'Webhook', conversations.history node named 'Fetch Message', users.info node named 'Get User'
... expand to see full code
// n8n Code Node: Transform Slack reaction payload into clean Asana task fields
// Place this node after the IF (emoji filter) node and before the Asana node
// Expects: Webhook node named 'Webhook', conversations.history node named 'Fetch Message', users.info node named 'Get User'
const webhookData = $node['Webhook'].json;
const messageData = $node['Fetch Message'].json;
const userData = $node['Get User'].json;
// Extract core fields
const channelId = webhookData.event.item.channel;
const rawTs = webhookData.event.item.ts;
const senderName = userData.user?.real_name || userData.user?.name || 'Unknown';
const messageText = messageData.messages?.[0]?.text || '(no message text found)';
// Build Slack permalink — remove the period from ts to match Slack URL format
const tsForUrl = rawTs.replace('.', '');
const slackWorkspace = 'acme'; // <-- replace with your Slack workspace subdomain
const permalink = `https://${slackWorkspace}.slack.com/archives/${channelId}/p${tsForUrl}`;
// Truncate message text for task name (Asana task names over 256 chars get cut off)
const MAX_TITLE_LENGTH = 200;
const taskName = messageText.length > MAX_TITLE_LENGTH
? messageText.substring(0, MAX_TITLE_LENGTH) + '…'
: messageText;
// Format notes field with context
const taskNotes = [
`Created from Slack message by ${senderName}`,
`Original message: ${permalink}`,
`Channel ID: ${channelId}`,
`Timestamp: ${new Date(parseFloat(rawTs) * 1000).toISOString()}`
].join('\n');
// Return clean object for downstream nodes
return [{
json: {
taskName,
taskNotes,
permalink,
senderName,
channelId,
rawTs,
messageText
}
}];api.slack.com/apps > [Your App] > Event Subscriptions > Subscribe to bot events
Subscribe to Reaction Added Events
Still in Event Subscriptions, scroll down to 'Subscribe to bot events' and add the event you want to trigger task creation. For emoji-based triggering, add 'reaction_added'. If you also want keyword-based triggers from message text, add 'message.channels' — but pick one for now to keep the logic clean. Click 'Save Changes' at the bottom of the page.
- 1Click 'Add Bot User Event' under 'Subscribe to bot events'
- 2Type 'reaction_added' and select it from the dropdown
- 3Click 'Save Changes' at the bottom of the page
api.slack.com/apps > [Your App] > OAuth & Permissions > Bot Token Scopes
Add OAuth Scopes and Install the App
Go to 'OAuth & Permissions' in the left sidebar. Under 'Bot Token Scopes', add the scopes your app needs: reactions:read, channels:history, and users:read (to resolve user IDs to real names for the Asana assignee field). Click 'Install to Workspace', then confirm the permissions. Copy the 'Bot User OAuth Token' — it starts with xoxb- and you'll use it in n8n to fetch message details.
- 1Click 'OAuth & Permissions' in the left sidebar
- 2Click 'Add an OAuth Scope' under 'Bot Token Scopes'
- 3Add: reactions:read, channels:history, users:read
- 4Click 'Install to Workspace' at the top of the page
- 5Click 'Allow' on the confirmation screen
- 6Copy the Bot User OAuth Token (starts with xoxb-)
n8n > Workflow Canvas > + Add Node > HTTP Request
Fetch the Original Message Text in n8n
The reaction_added event from Slack tells you WHO reacted and WHICH message, but it does not include the message text itself. You need a second node in n8n to call Slack's conversations.history API and fetch the actual message. Add an 'HTTP Request' node after your Webhook node. Set the method to GET and the URL to https://slack.com/api/conversations.history. Pass channel and timestamp (ts) from the webhook payload as query parameters, and add your xoxb- token as a Bearer token in the Authorization header.
- 1Click the '+' after the Webhook node to add a new node
- 2Search for 'HTTP Request' and select it
- 3Set Method to 'GET'
- 4Set URL to 'https://slack.com/api/conversations.history'
- 5Under 'Query Parameters', add: channel = {{$json.event.item.channel}} and latest = {{$json.event.item.ts}} and limit = 1 and inclusive = true
- 6Under 'Authentication', select 'Header Auth' and add: Authorization = Bearer xoxb-your-token
n8n > Workflow Canvas > + Add Node > IF
Filter by Specific Emoji
Add an 'IF' node after the HTTP Request node. This prevents every reaction from creating a task — only the emoji you designate (e.g. white_check_mark for ✅) should trigger task creation. Set the condition to check that $json.event.reaction equals white_check_mark. The emoji name in Slack's API uses underscores and no colons — so ✅ is white_check_mark, not :white_check_mark:.
- 1Click '+' after the HTTP Request node
- 2Search for 'IF' and select it
- 3Set Value 1 to: {{$node['Webhook'].json.event.reaction}}
- 4Set Condition to 'Equal'
- 5Set Value 2 to: white_check_mark
- 6Connect the 'true' output branch to the next step
n8n > Workflow Canvas > + Add Node > HTTP Request
Resolve the Slack User ID to a Name
The Slack event gives you a user ID like U04XYZ123, not a name. Add another HTTP Request node to call Slack's users.info API so you can put a real name in the Asana task notes or use it to match an Asana assignee. Set the URL to https://slack.com/api/users.info with the query parameter user = {{$node['Webhook'].json.event.user}} and the same Bearer token header.
- 1Click '+' after the IF node (true branch)
- 2Add another 'HTTP Request' node
- 3Set Method to 'GET'
- 4Set URL to 'https://slack.com/api/users.info'
- 5Add Query Parameter: user = {{$node['Webhook'].json.event.user}}
- 6Reuse the same Bearer token header
This Code node runs after the Webhook node and before the Asana node. It parses the raw Slack payload, builds the message permalink (by stripping the period from the ts value), truncates long messages to a clean task title, and outputs a structured object that later nodes can reference cleanly without complex nested expressions. Paste it as a 'Code' node between your IF node and the Asana node.
JavaScript — Code Node// n8n Code Node: Transform Slack reaction payload into clean Asana task fields▸ Show code
// n8n Code Node: Transform Slack reaction payload into clean Asana task fields // Place this node after the IF (emoji filter) node and before the Asana node // Expects: Webhook node named 'Webhook', conversations.history node named 'Fetch Message', users.info node named 'Get User'
... expand to see full code
// n8n Code Node: Transform Slack reaction payload into clean Asana task fields
// Place this node after the IF (emoji filter) node and before the Asana node
// Expects: Webhook node named 'Webhook', conversations.history node named 'Fetch Message', users.info node named 'Get User'
const webhookData = $node['Webhook'].json;
const messageData = $node['Fetch Message'].json;
const userData = $node['Get User'].json;
// Extract core fields
const channelId = webhookData.event.item.channel;
const rawTs = webhookData.event.item.ts;
const senderName = userData.user?.real_name || userData.user?.name || 'Unknown';
const messageText = messageData.messages?.[0]?.text || '(no message text found)';
// Build Slack permalink — remove the period from ts to match Slack URL format
const tsForUrl = rawTs.replace('.', '');
const slackWorkspace = 'acme'; // <-- replace with your Slack workspace subdomain
const permalink = `https://${slackWorkspace}.slack.com/archives/${channelId}/p${tsForUrl}`;
// Truncate message text for task name (Asana task names over 256 chars get cut off)
const MAX_TITLE_LENGTH = 200;
const taskName = messageText.length > MAX_TITLE_LENGTH
? messageText.substring(0, MAX_TITLE_LENGTH) + '…'
: messageText;
// Format notes field with context
const taskNotes = [
`Created from Slack message by ${senderName}`,
`Original message: ${permalink}`,
`Channel ID: ${channelId}`,
`Timestamp: ${new Date(parseFloat(rawTs) * 1000).toISOString()}`
].join('\n');
// Return clean object for downstream nodes
return [{
json: {
taskName,
taskNotes,
permalink,
senderName,
channelId,
rawTs,
messageText
}
}];n8n > Workflow Canvas > + Add Node > Asana > Create Task
Create the Asana Task
Add an Asana node after the user lookup. Select 'Create Task' as the operation. Connect your Asana account using a Personal Access Token from app.asana.com/0/my-apps. Map the task name to the message text, set the project ID to your target Asana project, and add the Slack message URL and sender name to the task notes field. The Slack message permalink format is https://your-workspace.slack.com/archives/CHANNEL_ID/p{ts_without_period} — the code node in the pro tip handles this transformation.
- 1Click '+' after the users.info HTTP Request node
- 2Search for 'Asana' and select it
- 3Set Operation to 'Create Task'
- 4Click 'Create New Credential' and paste your Asana Personal Access Token
- 5Set Name to: {{$node['Fetch Message'].json.messages[0].text}}
- 6Set Projects to your Asana project GID (find this in the Asana project URL)
- 7Set Notes to: 'From Slack — {{$node['Get User'].json.user.real_name}}\nhttps://your-workspace.slack.com/archives/{{$node["Webhook"].json.event.item.channel}}/p{{$node["Webhook"].json.event.item.ts.replace(".","")}}'
n8n > Workflow > Activate toggle (top right) | api.slack.com/apps > Event Subscriptions
Activate the Workflow and Switch to Production Webhook
Click 'Activate' in the top right of the n8n workflow editor. Once active, n8n generates a stable production webhook URL that doesn't require the workflow to be in test mode. Go back to your Slack app's Event Subscriptions page and replace the test URL with the production URL. The production URL format is the same but uses /webhook/ instead of /webhook-test/.
- 1Click the 'Inactive' toggle in the top right of n8n to activate the workflow
- 2Copy the production webhook URL from the Webhook node (labeled 'Production URL')
- 3Go back to api.slack.com/apps > [Your App] > Event Subscriptions
- 4Replace the test URL with the production URL
- 5Click 'Save Changes' — Slack will re-verify the URL automatically
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 want full control over the data transformation between Slack and Asana and you're comfortable editing a workflow visually or touching a bit of JavaScript. The reaction_added event gives you a raw user ID, a timestamp, and a channel — no message text. n8n lets you chain three API calls (conversations.history, users.info, then Asana task creation) in a single workflow with clean error handling at each step. The one scenario where you'd skip n8n: if your team already uses Asana's native Slack integration. It handles basic task creation from messages with zero setup and no maintenance burden.
n8n Cloud's starter plan costs $20/month for 2,500 executions. This workflow uses 1 execution per reaction event. A 15-person team creating 10 tasks per day via emoji reactions burns through ~300 executions/month — well within the free community tier if you self-host, or the $20 plan on cloud. Self-hosting on a $6/month VPS (DigitalOcean, Hetzner) brings the total cost to $6/month and removes all execution caps. Zapier would cost $29.99/month minimum for the same volume (Zaps with multi-step paths require a paid plan), and Make's Core plan at $9/month handles this comfortably but requires more workarounds for the multi-API-call pattern.
Make handles the Slack-to-Asana connection with less setup friction — its Slack module exposes a 'Watch Reactions' trigger that already parses the event for you, skipping the manual webhook verification step. Zapier has a 'New Reaction Added' trigger in Slack that works with no-code configuration, but you can't chain the users.info lookup without a Code by Zapier step. Power Automate has no clean Slack connector for reaction events — you'd need to use the Slack webhook manually, which puts you back to the same complexity as n8n without the flexibility. Pipedream's Slack source handles reaction_added natively and the Node.js steps make the API chaining clean, but it costs more at volume. n8n wins here because you own the infrastructure, the multi-step API chaining is native, and you can run unlimited workflows for a flat hosting cost.
Three things you'll hit after setup. First: Slack's conversations.history API returns messages in reverse chronological order — if you pass a ts that matches exactly and inclusive=true, you get the right message, but any clock drift or incorrect ts formatting returns an empty array. Test with the Slack API explorer before trusting your expressions. Second: Asana tasks created via API land in the project but have no section assigned by default. If your project uses sections heavily, tasks will pile up in the 'Untitled Section' bucket and get ignored. Add the memberships field with a section GID to place tasks correctly. Third: Slack will disable event delivery to your endpoint if it receives HTTP 500 responses more than a handful of times — this means one bad Asana API call (expired token, wrong GID) can silently kill the entire integration. Add error output handling on every node and route failures to a dedicated Slack DM or a log so you know when the workflow breaks.
Ideas for what to build next
- →Route Different Emojis to Different Asana Projects — Add a Switch node after the IF node to send ✅ reactions to your 'Sprint Backlog' project and 🐛 reactions to your 'Bug Reports' project. Each branch connects to its own Asana node with a different project GID.
- →Assign Tasks Based on Slack User — Build a lookup table (a simple JSON object in a Code node) that maps Slack user IDs to Asana user GIDs. When a task is created, the workflow automatically assigns it to the person whose message was reacted to.
- →Post a Confirmation Back to Slack — Add a Slack node at the end of the workflow that posts a threaded reply on the original message with the Asana task URL — so the team knows the task was captured without having to check Asana manually.
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