Intermediate~20 min setupCommunication & Project ManagementVerified April 2026
Slack logo
Asana logo

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-time

Use case type

routing

Real-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.

/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.

Slack Workspace Admin access or permission to create and install custom Slack apps
Asana Personal Access Token from app.asana.com/0/my-apps with access to the target project
n8n instance with a publicly reachable URL (cloud, VPS, or local with ngrok) — Slack cannot reach localhost directly
Slack Bot Token Scopes: reactions:read, channels:history, users:read installed on your workspace
Asana project GID for the destination project (visible in the project URL on app.asana.com)

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Task Namename
Project GIDprojects
6 optional fields▸ show
Task Notesnotes
Due Datedue_on
Assignee GIDassignee
Slack Message Timestamp
Slack Channel ID
Sender Display Name

Step-by-Step Setup

1

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.

  1. 1Go to api.slack.com/apps and click the green 'Create New App' button
  2. 2Select 'From scratch'
  3. 3Enter app name (e.g. 'n8n Task Bot') and select your Slack workspace
  4. 4Click 'Create App'
What you should see: You land on the app configuration page showing 'Basic Information' with your app name and credentials visible.
Common mistake — You must be a Slack Workspace Admin or have permission to install apps. If you see 'App installation requires admin approval', contact your Slack admin before continuing — you can't complete the webhook step without install rights.

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
  }
}];
2

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.

  1. 1Click '+ Add Node' in the center canvas
  2. 2Search for 'Webhook' and select it
  3. 3Set HTTP Method to 'POST'
  4. 4Click 'Copy URL' next to the test webhook URL
  5. 5Click 'Execute Node' to put it in listening mode
What you should see: The Webhook node shows 'Waiting for you to call the Test URL' in orange. The URL is visible and copyable in the node panel.
Common mistake — n8n generates two URLs: a test URL (used while building) and a production URL (used after activation). Do NOT paste the production URL into Slack yet — you won't be able to verify events against a live workflow until setup is complete.
3

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.

  1. 1Click 'Event Subscriptions' in the left sidebar
  2. 2Toggle 'Enable Events' to On
  3. 3Paste your n8n test webhook URL into the 'Request URL' field
  4. 4Wait for the green 'Verified' checkmark to appear
What you should see: A green 'Verified' badge appears next to the Request URL field within 3-5 seconds.
Common mistake — If verification fails with 'Your URL didn't respond with the value of the challenge parameter', your n8n instance is not publicly reachable. Local n8n instances need ngrok or Cloudflare Tunnel to expose a public URL. Cloud n8n instances work immediately.

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
  }
}];
4

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.

  1. 1Click 'Add Bot User Event' under 'Subscribe to bot events'
  2. 2Type 'reaction_added' and select it from the dropdown
  3. 3Click 'Save Changes' at the bottom of the page
What you should see: 'reaction_added' appears in the bot events list with the required OAuth scopes listed beside it (reactions:read, channels:history).
5

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.

  1. 1Click 'OAuth & Permissions' in the left sidebar
  2. 2Click 'Add an OAuth Scope' under 'Bot Token Scopes'
  3. 3Add: reactions:read, channels:history, users:read
  4. 4Click 'Install to Workspace' at the top of the page
  5. 5Click 'Allow' on the confirmation screen
  6. 6Copy the Bot User OAuth Token (starts with xoxb-)
What you should see: You see a green 'Bot User OAuth Token' field with a visible xoxb- token. The app now appears in your Slack workspace under Apps.
Common mistake — users:read is required to resolve Slack user IDs to names. Without it, your Asana task notes will show a raw ID like U04XYZ instead of 'Jamie Chen'. Add it now — re-installing later requires re-verification.
6

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.

  1. 1Click the '+' after the Webhook node to add a new node
  2. 2Search for 'HTTP Request' and select it
  3. 3Set Method to 'GET'
  4. 4Set URL to 'https://slack.com/api/conversations.history'
  5. 5Under 'Query Parameters', add: channel = {{$json.event.item.channel}} and latest = {{$json.event.item.ts}} and limit = 1 and inclusive = true
  6. 6Under 'Authentication', select 'Header Auth' and add: Authorization = Bearer xoxb-your-token
What you should see: When you run the node in test mode, you see a JSON response containing the messages array with the full message text, user ID, and timestamp.
Common mistake — The ts field from Slack is a Unix timestamp with microseconds (e.g. 1715000000.000100). Use it as-is for the latest parameter — do not convert it to a date or the API will return the wrong message.
7

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:.

  1. 1Click '+' after the HTTP Request node
  2. 2Search for 'IF' and select it
  3. 3Set Value 1 to: {{$node['Webhook'].json.event.reaction}}
  4. 4Set Condition to 'Equal'
  5. 5Set Value 2 to: white_check_mark
  6. 6Connect the 'true' output branch to the next step
What you should see: The IF node shows two output branches: 'true' (emoji matched) and 'false' (all other reactions). Only the true branch continues.
Common mistake — Slack emoji names are case-sensitive and strip colons. Test by reacting with ✅ in Slack and checking what arrives in the Webhook node — copy the exact reaction string from the payload rather than guessing the name.
Slack
SL
trigger
filter
Condition
matches criteria?
yes — passes through
no — skipped
Asana
AS
notified
8

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.

  1. 1Click '+' after the IF node (true branch)
  2. 2Add another 'HTTP Request' node
  3. 3Set Method to 'GET'
  4. 4Set URL to 'https://slack.com/api/users.info'
  5. 5Add Query Parameter: user = {{$node['Webhook'].json.event.user}}
  6. 6Reuse the same Bearer token header
What you should see: The response contains the user's real_name and display_name fields, e.g. 'Jamie Chen'.

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
  }
}];
9

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.

  1. 1Click '+' after the users.info HTTP Request node
  2. 2Search for 'Asana' and select it
  3. 3Set Operation to 'Create Task'
  4. 4Click 'Create New Credential' and paste your Asana Personal Access Token
  5. 5Set Name to: {{$node['Fetch Message'].json.messages[0].text}}
  6. 6Set Projects to your Asana project GID (find this in the Asana project URL)
  7. 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(".","")}}'
What you should see: After running in test mode, you see the new task appear in your Asana project within 3-5 seconds with the message text as the title and the Slack link in the notes.
Common mistake — The Asana project GID is the number in the URL when you open a project: app.asana.com/0/PROJECT_GID/list. Do not use the project name — the API only accepts the numeric GID.
10

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/.

  1. 1Click the 'Inactive' toggle in the top right of n8n to activate the workflow
  2. 2Copy the production webhook URL from the Webhook node (labeled 'Production URL')
  3. 3Go back to api.slack.com/apps > [Your App] > Event Subscriptions
  4. 4Replace the test URL with the production URL
  5. 5Click 'Save Changes' — Slack will re-verify the URL automatically
What you should see: Slack shows 'Verified' next to the new URL. The n8n workflow shows 'Active' in green. Reacting with ✅ in Slack now creates a task in Asana without any manual trigger.
Common mistake — After switching to the production URL, also invite your Slack bot to every channel you want it to monitor. Go to the channel, type /invite @n8n-task-bot. If the bot isn't in the channel, Slack will not deliver reaction events from that channel.

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 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.

Cost

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.

Tradeoffs

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 ProjectsAdd 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 UserBuild 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 SlackAdd 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

Was this guide helpful?
Slack + Asana overviewn8n profile →