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

How to Assign Todoist Tasks from Slack Mentions with n8n

Monitors specific Slack channels for user mentions and automatically creates an assigned Todoist task with the message text, channel context, and correct assignee.

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

Best for

Teams that assign work verbally in Slack project channels and lose track of who owns what by the next day.

Not ideal for

Teams that need two-way sync — if Todoist task completion should close a Slack thread, this workflow won't do that alone.

Sync type

real-time

Use case type

routing

Real-World Example

💡

A 12-person product agency uses dedicated Slack channels per client — #client-acme, #client-globex — and mentions teammates when handing off work mid-conversation. Before this workflow, those mentions got buried in channel history within hours and tasks were missed entirely. Now every @mention in those channels creates a Todoist task assigned to that person under the matching project, with the Slack message as the task description.

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 to create and install a custom Slack App at api.slack.com/apps
Todoist account with API access — get your token at todoist.com/app/settings/integrations/developer
n8n instance accessible via a public HTTPS URL so Slack can reach the webhook endpoint (self-hosted or n8n Cloud)
Todoist project already created with all team members added as collaborators — Todoist can only assign tasks to existing project members
Slack bot scopes configured: app_mentions:read, channels:history, users:read — without these the bot token will be rejected

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Task Contentcontent
Project IDproject_id
Assignee Emailassignee
Slack Channel ID
Mentioned User Slack ID
4 optional fields▸ show
Due Datedue_string
Slack Message Timestamp
Slack Message Linkdescription
Prioritypriority

Step-by-Step Setup

1

api.slack.com/apps > Create New App > Event Subscriptions

Create a Slack App and Enable Event Subscriptions

Go to api.slack.com/apps and click 'Create New App', then choose 'From scratch'. Name it something like 'n8n Task Bot' and select your workspace. You need this custom app to receive mention events via webhook — n8n's built-in Slack OAuth credential does not expose event subscriptions without a custom app. Once created, go to 'Event Subscriptions' in the left sidebar and toggle it on.

  1. 1Go to api.slack.com/apps and click 'Create New App'
  2. 2Select 'From scratch', enter app name, choose your workspace
  3. 3Click 'Event Subscriptions' in the left sidebar
  4. 4Toggle 'Enable Events' to On — leave the Request URL blank for now
What you should see: You should see the Event Subscriptions page with the toggle set to On and a yellow 'Request URL' field waiting for input.
Common mistake — Do not install the app to your workspace yet. Installing before configuring scopes will require you to reinstall it later, which resets the bot token.

This single Code node handles three things at once: extracts the mentioned user ID from Slack's bracket syntax, strips the bot mention from the task content so it reads cleanly, detects urgency keywords to set Todoist priority, and constructs the Slack message deep link. Paste this into the first Code node (Step 6) — it expects the raw Slack webhook body at $input.first().json.body.

JavaScript — Code Node// n8n Code Node — Parse Slack Mention and Prepare Todoist Fields
▸ Show code
// n8n Code Node — Parse Slack Mention and Prepare Todoist Fields
// Paste into the Code node (Step 6). Language: JavaScript.
const event = $input.first().json.body.event;

... expand to see full code

// n8n Code Node — Parse Slack Mention and Prepare Todoist Fields
// Paste into the Code node (Step 6). Language: JavaScript.

const event = $input.first().json.body.event;

// Extract all user mentions from the message text
// Slack format: <@U012AB3CD> for user mentions
const mentionRegex = /<@(U[A-Z0-9]+)>/g;
const allMentions = [...event.text.matchAll(mentionRegex)].map(m => m[1]);

// The bot's own user ID — set this from your Slack App config (Bot User ID)
// Found at: api.slack.com/apps > [App] > App Home > App ID section
const BOT_USER_ID = process.env.SLACK_BOT_USER_ID || 'UBOTID000';

// The first mention that is NOT the bot is the intended assignee
const mentionedUserId = allMentions.find(id => id !== BOT_USER_ID) || null;

if (!mentionedUserId) {
  throw new Error('No non-bot user mention found in message. Skipping task creation.');
}

// Clean the message text: remove all <@USERID> patterns and trim whitespace
const cleanedText = event.text
  .replace(/<@[A-Z0-9]+>/g, '')
  .replace(/\s+/g, ' ')
  .trim();

// Detect urgency keywords to set Todoist priority
// Todoist priority: 1 = normal, 2 = medium, 3 = high, 4 = urgent
const urgentKeywords = ['urgent', 'asap', 'immediately', 'critical', 'blocker'];
const highKeywords = ['today', 'eod', 'end of day', 'by noon'];
const lowerText = cleanedText.toLowerCase();

let priority = 1;
if (urgentKeywords.some(kw => lowerText.includes(kw))) {
  priority = 4;
} else if (highKeywords.some(kw => lowerText.includes(kw))) {
  priority = 3;
}

// Construct Slack deep link to original message
// Format: https://workspace.slack.com/archives/CHANNELID/pTIMESTAMP
// The ts field uses a dot (e.g. 1710234567.000100) — deep link uses p + digits without dot
const tsForLink = event.ts.replace('.', '');
const SLACK_WORKSPACE_DOMAIN = process.env.SLACK_WORKSPACE_DOMAIN || 'yourworkspace';
const slackMessageLink = `https://${SLACK_WORKSPACE_DOMAIN}.slack.com/archives/${event.channel}/p${tsForLink}`;

return [
  {
    json: {
      mentionedUserId,
      channelId: event.channel,
      messageText: cleanedText,
      priority,
      slackMessageLink,
      originalTs: event.ts,
      triggeredByUserId: event.user
    }
  }
];
2

n8n Canvas > + Add Node > Trigger > Webhook

Create the n8n Webhook Node

Open n8n and create a new workflow. Add a 'Webhook' node as the first node — this is under the 'Trigger' section in the node panel. Set the HTTP Method to POST and leave the path as the auto-generated value. Copy the 'Test URL' shown in the node — you'll paste this into Slack's Event Subscriptions page. Keep this node open in test mode so Slack can verify the endpoint.

  1. 1Click the '+' button on the blank canvas
  2. 2Search for 'Webhook' and select it under Trigger nodes
  3. 3Set HTTP Method to 'POST'
  4. 4Click 'Listen for Test Event' to activate the test listener
  5. 5Copy the full Test URL from the node panel
What you should see: The Webhook node should show 'Waiting for test event...' with a green pulsing indicator and a URL in the format https://your-n8n-instance.com/webhook-test/[uuid].
Common mistake — Use the Test URL only during setup. Once you go live, swap it for the Production URL — Slack's verification only needs to hit the endpoint once, but all real events must go to the Production URL.
3

api.slack.com/apps > [Your App] > Event Subscriptions > Subscribe to Bot Events

Paste the Webhook URL into Slack and Subscribe to Events

Go back to the Slack app config page and paste the n8n webhook Test URL into the 'Request URL' field under Event Subscriptions. Slack will immediately send a challenge request to verify the endpoint. n8n will automatically respond to this challenge — you'll see it captured in the Webhook node in n8n. Once Slack shows a green 'Verified' checkmark, scroll down to 'Subscribe to bot events' and add the event you need.

  1. 1Paste the n8n Test URL into the 'Request URL' field
  2. 2Wait for the green 'Verified' badge to appear next to the URL
  3. 3Scroll to 'Subscribe to bot events' and click 'Add Bot User Event'
  4. 4Search for and select 'app_mention'
  5. 5Click 'Save Changes' at the bottom of the page
What you should see: The Request URL field should show a green 'Verified' checkmark, and 'app_mention' should appear in the bot events list.
Common mistake — Copy the webhook URL carefully — it expires if you regenerate it, and any scenarios using the old URL will silently stop working.
4

api.slack.com/apps > [Your App] > OAuth & Permissions > Scopes

Set OAuth Scopes and Install the App

Go to 'OAuth & Permissions' in the left sidebar of your Slack app config. Under 'Bot Token Scopes', add the required scopes. Then scroll up and click 'Install to Workspace'. After approval, copy the 'Bot User OAuth Token' — it starts with xoxb-. You'll add this to n8n's Slack credential.

  1. 1Click 'OAuth & Permissions' in the left sidebar
  2. 2Under 'Bot Token Scopes', click 'Add an OAuth Scope'
  3. 3Add: app_mentions:read, channels:history, users:read
  4. 4Click 'Install to Workspace' at the top of the page
  5. 5Approve the permissions and copy the 'Bot User OAuth Token'
What you should see: You should see the Bot User OAuth Token displayed starting with 'xoxb-'. Copy it — you'll only see it once without scrolling back here.
Common mistake — The app_mentions:read scope only fires when the bot itself is @mentioned. If you want to detect @mentions of other users in a channel (not the bot), you also need channels:history and will need to parse the message text manually — which this workflow does in Step 6.
5

n8n > Credentials > New > Slack API

Add Slack Credential to n8n and Invite Bot to Channels

In n8n, go to Credentials in the left sidebar and create a new 'Slack API' credential. Paste the xoxb- token into the 'Access Token' field and save. Back in Slack, go to each project channel you want to monitor and type /invite @YourBotName to add the bot. The bot must be a member of a channel to receive app_mention events from it.

  1. 1In n8n, click 'Credentials' in the left sidebar
  2. 2Click '+ Add Credential' and search for 'Slack API'
  3. 3Paste the xoxb- Bot Token into the 'Access Token' field
  4. 4Click 'Save' and confirm the green checkmark appears
  5. 5In each target Slack channel, type /invite @YourBotName and press Enter
What you should see: The Slack API credential in n8n should show a green 'Connection tested successfully' message. In Slack, each channel should show '[BotName] has joined the channel'.
Common mistake — If you skip inviting the bot to a channel, Slack will silently drop app_mention events from that channel — no error, just no trigger. Test by @mentioning the bot in the channel and watching for the webhook to fire in n8n.
message template
🔔 New Record: {{text}} {{user}}
channel: {{channel}}
ts: {{ts}}
#sales
🔔 New Record: Jane Smith
Company: Acme Corp
6

n8n Canvas > + Add Node > Core > Code

Parse the Mention and Extract the Tagged User

Add a 'Code' node after the Webhook node. The raw Slack event payload contains the message text with user mentions in the format <@U012AB3CD>. You need to extract the mentioned user's Slack ID from this string and map it to a Todoist assignee. This Code node will also extract the channel ID so you can route to the right Todoist project. Paste the JavaScript from the Pro Tip section below into this node.

  1. 1Click '+' after the Webhook node to add a new node
  2. 2Search for '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 it parses a sample Slack payload
What you should see: The Code node output should show extracted fields: mentionedUserId (e.g. U012AB3CD), channelId, messageText, and timestamp — all as separate fields in the output item.
Common mistake — Slack sends user IDs like <@U012AB3CD>, not display names. Do not try to pass this raw string to Todoist as the assignee — the next step resolves it to an email address which Todoist uses for assignment.
7

n8n Canvas > + Add Node > Core > HTTP Request

Resolve Slack User ID to Email with Slack HTTP Node

Add an 'HTTP Request' node to call the Slack users.info API. This converts the Slack user ID you extracted in Step 6 into an email address, which is what Todoist needs to assign a task. The endpoint is https://slack.com/api/users.info and requires the user ID as a query parameter. Use the xoxb- token as a Bearer token in the Authorization header.

  1. 1Add an 'HTTP Request' node after the Code node
  2. 2Set Method to 'GET'
  3. 3Set URL to 'https://slack.com/api/users.info'
  4. 4Under Query Parameters, add 'user' = '{{ $json.mentionedUserId }}'
  5. 5Under Headers, add 'Authorization' = 'Bearer xoxb-your-token-here'
What you should see: The HTTP Request node should return a JSON object where user.profile.email contains the Slack user's email address, e.g. '[email protected]'.
Common mistake — users.info is rate-limited to Tier 4 (100 requests per minute). For high-traffic channels this is fine, but if you have bursts of 100+ mentions per minute, requests will start returning 429 errors. Add a 'Wait' node set to 1 second before this node if you hit that ceiling.

This single Code node handles three things at once: extracts the mentioned user ID from Slack's bracket syntax, strips the bot mention from the task content so it reads cleanly, detects urgency keywords to set Todoist priority, and constructs the Slack message deep link. Paste this into the first Code node (Step 6) — it expects the raw Slack webhook body at $input.first().json.body.

JavaScript — Code Node// n8n Code Node — Parse Slack Mention and Prepare Todoist Fields
▸ Show code
// n8n Code Node — Parse Slack Mention and Prepare Todoist Fields
// Paste into the Code node (Step 6). Language: JavaScript.
const event = $input.first().json.body.event;

... expand to see full code

// n8n Code Node — Parse Slack Mention and Prepare Todoist Fields
// Paste into the Code node (Step 6). Language: JavaScript.

const event = $input.first().json.body.event;

// Extract all user mentions from the message text
// Slack format: <@U012AB3CD> for user mentions
const mentionRegex = /<@(U[A-Z0-9]+)>/g;
const allMentions = [...event.text.matchAll(mentionRegex)].map(m => m[1]);

// The bot's own user ID — set this from your Slack App config (Bot User ID)
// Found at: api.slack.com/apps > [App] > App Home > App ID section
const BOT_USER_ID = process.env.SLACK_BOT_USER_ID || 'UBOTID000';

// The first mention that is NOT the bot is the intended assignee
const mentionedUserId = allMentions.find(id => id !== BOT_USER_ID) || null;

if (!mentionedUserId) {
  throw new Error('No non-bot user mention found in message. Skipping task creation.');
}

// Clean the message text: remove all <@USERID> patterns and trim whitespace
const cleanedText = event.text
  .replace(/<@[A-Z0-9]+>/g, '')
  .replace(/\s+/g, ' ')
  .trim();

// Detect urgency keywords to set Todoist priority
// Todoist priority: 1 = normal, 2 = medium, 3 = high, 4 = urgent
const urgentKeywords = ['urgent', 'asap', 'immediately', 'critical', 'blocker'];
const highKeywords = ['today', 'eod', 'end of day', 'by noon'];
const lowerText = cleanedText.toLowerCase();

let priority = 1;
if (urgentKeywords.some(kw => lowerText.includes(kw))) {
  priority = 4;
} else if (highKeywords.some(kw => lowerText.includes(kw))) {
  priority = 3;
}

// Construct Slack deep link to original message
// Format: https://workspace.slack.com/archives/CHANNELID/pTIMESTAMP
// The ts field uses a dot (e.g. 1710234567.000100) — deep link uses p + digits without dot
const tsForLink = event.ts.replace('.', '');
const SLACK_WORKSPACE_DOMAIN = process.env.SLACK_WORKSPACE_DOMAIN || 'yourworkspace';
const slackMessageLink = `https://${SLACK_WORKSPACE_DOMAIN}.slack.com/archives/${event.channel}/p${tsForLink}`;

return [
  {
    json: {
      mentionedUserId,
      channelId: event.channel,
      messageText: cleanedText,
      priority,
      slackMessageLink,
      originalTs: event.ts,
      triggeredByUserId: event.user
    }
  }
];
8

n8n Canvas > + Add Node > Core > Code

Map Channel ID to Todoist Project ID

Add another 'Code' node to map the Slack channel ID to a Todoist project ID. You'll hardcode this mapping as a JavaScript object — each Slack channel ID maps to one Todoist project ID. Get your Todoist project IDs from the Todoist API at todoist.com/API/v9/projects using your API token. This is a deliberate design choice: it keeps the routing logic explicit and editable without touching the workflow structure.

  1. 1Add a second 'Code' node after the HTTP Request node
  2. 2Paste the channel-to-project mapping object (see Pro Tip code)
  3. 3Add your actual Slack channel IDs as keys and Todoist project IDs as values
  4. 4Add a fallback project ID for channels not in the map
  5. 5Click 'Test Step' to confirm the correct project ID is returned
What you should see: The node output should include a 'todoistProjectId' field containing a valid numeric Todoist project ID based on the incoming channel ID.
Common mistake — Slack channel IDs start with C (e.g. C04XYZ123). Do not use the channel name — names can be changed by any workspace admin, which breaks the mapping silently. Always use the ID, which is stable.
Slack fields
text
user
channel
ts
thread_ts
available as variables:
1.props.text
1.props.user
1.props.channel
1.props.ts
1.props.thread_ts
9

n8n Canvas > + Add Node > Todoist > Create Task

Create the Todoist Task

Add a 'Todoist' node and select the 'Create Task' operation. Connect it using your Todoist API credential (create this under n8n Credentials using your Todoist API token from todoist.com/app/settings/integrations/developer). Map the task content, project ID, assignee email, and due date. The assignee email from Step 7 goes into the 'Assignee' field — Todoist will match it to a project collaborator.

  1. 1Add a 'Todoist' node after the second Code node
  2. 2Select 'Create Task' as the operation
  3. 3Set 'Project ID' to '{{ $json.todoistProjectId }}'
  4. 4Set 'Content' to the message text extracted in Step 6
  5. 5Set 'Assignee' to the email from the HTTP Request node: '{{ $node["HTTP Request"].json.user.profile.email }}'
What you should see: The Todoist node test should return a task object with an id field, a content field matching the Slack message, and an assignee_id field that is not null.
Common mistake — Todoist task assignment only works if the assignee is already a collaborator on that specific project. If the email resolves but the assignee_id comes back null, the user hasn't been added to the Todoist project yet.
10

n8n Canvas > + Add Node > Core > IF

Add a Filter to Ignore Bot Messages and Non-Target Channels

Add an 'IF' node between the Webhook node and the Code parser. This prevents the workflow from creating tasks when the bot itself posts a message (which would cause a loop) and limits execution to only your configured channels. Check that the event subtype is not 'bot_message' and that the channel ID exists in your allowed list. Without this filter, any app_mention anywhere in the workspace will trigger task creation.

  1. 1Add an 'IF' node directly after the Webhook node
  2. 2Add Condition 1: '{{ $json.body.event.subtype }}' is not equal to 'bot_message'
  3. 3Add Condition 2: '{{ $json.body.event.channel }}' contains one of your target channel IDs
  4. 4Set the combinator to 'AND'
  5. 5Connect the 'true' output to the Code parser node; leave the 'false' output unconnected
What you should see: When you test with a bot-originated message, the IF node should route to the 'false' branch and stop. A real user mention in a target channel should route to 'true' and continue.
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.
Slack
SL
trigger
filter
Condition
matches criteria?
yes — passes through
no — skipped
Todoist
TO
notified
11

n8n Canvas > Webhook Node > Production URL | api.slack.com/apps > Event Subscriptions

Switch to Production URL and Activate Workflow

Go back to the Webhook node and copy the Production URL (not the Test URL). Update the Request URL in your Slack app's Event Subscriptions page to this Production URL. Slack will re-verify it — your active workflow will respond to the challenge automatically. Then click 'Activate' in the top-right of the n8n canvas to turn the workflow on.

  1. 1Click the Webhook node and copy the Production URL
  2. 2Go to api.slack.com/apps > your app > Event Subscriptions
  3. 3Replace the Test URL with the Production URL and wait for 'Verified'
  4. 4Return to n8n and click the 'Activate' toggle in the top-right corner
  5. 5Confirm the workflow status shows 'Active' in green
What you should see: The n8n workflow should show as 'Active'. The Slack Event Subscriptions page should show 'Verified' next to the Production URL. A test @mention in a target channel should create a Todoist task within 3-5 seconds.
Common mistake — Copy the webhook URL carefully — it expires if you regenerate it, and any scenarios using the old URL will silently stop working.

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 your team already self-hosts infrastructure or uses n8n Cloud, and if you need the Slack user ID → email → Todoist assignee resolution chain to be customizable. The Code node is the reason n8n wins here — you need to parse Slack's bracket-syntax mentions, strip bot IDs, detect urgency keywords, and construct deep links, all in one step. That's 5 minutes of JavaScript, not a chain of 6 separate no-code nodes. The one scenario where you'd pick a different platform: if your team has zero JavaScript tolerance and someone needs to maintain this workflow, Zapier's multi-step Zap with a Formatter step handles the basics without any code — just at higher cost and with less control over edge cases.

Cost

Cost math for n8n: each Slack mention runs the workflow once, consuming 1 execution across roughly 5 nodes. On n8n Cloud's Starter plan ($20/month), you get 2,500 executions per month — that covers about 500 @mentions/month with room to spare. At 100 mentions per day (2,000–3,000/month), you're looking at the $50/month Pro plan. Self-hosted n8n is free regardless of volume, so if you're already running n8n on a $10 DigitalOcean droplet, this workflow costs nothing extra. Zapier by comparison charges per task, not per execution — at 500 mentions/month on Zapier's Professional plan ($49/month), you'd burn through tasks fast because the multi-step lookup counts each action separately.

Tradeoffs

Make handles the Slack webhook trigger cleanly and its HTTP module works fine for the users.info lookup — it's a valid alternative and the free tier covers 1,000 operations per month. Zapier has a native Slack trigger that detects new mentions without a custom app setup, which genuinely saves 20 minutes of Slack app config — worth it if you're already on Zapier. Power Automate has a Slack connector but it's shallow: no app_mention event type support as of 2024, so you'd be polling channels, which introduces minutes of lag. Pipedream has an excellent Slack event source that handles app verification automatically, and the code step is just as capable as n8n's — Pipedream is a real alternative if you prefer a hosted environment with Git-backed version control. n8n is still the right call here because the JavaScript Code node, the self-hostable architecture, and the execution-based (not task-based) pricing model fit this use case better than any of them for teams doing moderate volume.

Three things you'll hit after setup: First, Slack retries event delivery if your n8n instance doesn't respond within 3 seconds. Under load or cold-start conditions, this creates duplicate tasks. Store processed event_ids in n8n static data and check before executing — two lines of code that prevent a genuine operational headache. Second, the Todoist API rate limit is 450 requests per 15 minutes per user token. That's generous, but if you have multiple workflows sharing the same Todoist API token (common when a team expands automation), you'll hit 429 errors and tasks will fail silently. Use separate tokens per workflow. Third, Slack's users.info API does not return emails for users with 'email display' restricted by their workspace admin. On enterprise Slack plans with strict privacy settings, roughly 10–20% of user lookups may return a profile with no email field — build a fallback that appends the raw Slack user ID to the task description so the task at least gets created and is traceable.

Ideas for what to build next

  • Post a Slack Confirmation When the Task is CreatedAdd a Slack 'Post Message' node after the Todoist node to reply in the thread confirming the task was created and linking directly to it in Todoist. Closes the loop for the person who sent the mention.
  • Sync Todoist Task Completion Back to SlackUse a second n8n workflow with a Todoist webhook to detect when the assigned task is marked complete, then post a completion message in the original Slack channel. Turns the one-way flow into a full feedback loop.
  • Add Due Date Parsing from Natural Language in the Slack MessageExtend the Code node to extract date phrases like 'by Friday' or 'before 3pm' using a simple regex or the Todoist due_string field which accepts natural language. Reduces the number of tasks that sit in Todoist with no due date.

Related guides

Was this guide helpful?
Slack + Todoist overviewn8n profile →