Intermediate~20 min setupCommunication & ProductivityVerified April 2026
Slack logo
Notion logo

How to Create Notion Tasks from Slack with n8n

When a Slack message gets a specific emoji reaction, n8n captures it via webhook and creates a new task entry in a Notion database with the message text, sender, channel, and timestamp.

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

Best for

Engineering or product teams who surface action items inside Slack and need them tracked in Notion without copy-pasting.

Not ideal for

Teams who want two-way sync — when a Notion task updates, this workflow does not reflect that back into Slack.

Sync type

real-time

Use case type

routing

Real-World Example

💡

A 12-person product team at a B2B SaaS company uses this to capture feature requests that surface in #customer-feedback by reacting with a 📋 emoji. Before this workflow, a PM manually checked the channel twice a day and copy-pasted requests into Notion — missing roughly 3-4 items per week. Now every reacted message becomes a Notion task within 10 seconds.

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.

n8n instance running (self-hosted or n8n Cloud) with a publicly accessible URL — Slack cannot reach localhost
Slack workspace admin access or permission to create and install Slack apps
Notion Internal Integration Token with access to the target database (created at notion.so/my-integrations)
A Notion database with at minimum a Title property and optionally Status, URL, and Date properties already set up
Slack bot token scopes: channels:history, reactions:read, channels:read, reactions:write

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Task Title
7 optional fields▸ show
Status
Source Channel
Slack Message URL
Requested By (User ID)
Created Date
Message Timestamp
Emoji Used

Step-by-Step Setup

1

api.slack.com/apps > Your App > Event Subscriptions

Create a Slack App and enable Event Subscriptions

Go to api.slack.com/apps and click 'Create New App'. Choose 'From scratch', name it something like 'Notion Task Bot', and select your workspace. You need a dedicated app here because n8n's webhook URL must be registered with Slack's Event Subscriptions system — you can't do this through a generic OAuth token. Once the app is created, navigate to 'Event Subscriptions' in the left sidebar and toggle 'Enable Events' to On.

  1. 1Go to api.slack.com/apps and click 'Create New App'
  2. 2Select 'From scratch'
  3. 3Enter an app name and select your target workspace
  4. 4Click 'Create App'
  5. 5In the left sidebar, click 'Event Subscriptions'
  6. 6Toggle 'Enable Events' to On — leave the Request URL blank for now
What you should see: You should see the Event Subscriptions page with a green 'Enabled' toggle and an empty Request URL field waiting for your webhook.
Common mistake — Do not install the app to your workspace yet. If you install before setting the correct scopes, you'll need to reinstall — and that resets any existing bot tokens.

Paste this into the Code node in Step 7. It pulls the message text, formats the Slack timestamp into a human-readable date, builds the Slack deep-link URL from the channel and timestamp, truncates titles over 100 characters so Notion doesn't reject them, and outputs a clean object ready for the Notion node.

JavaScript — Code Node// Code node — runs after conversations.history HTTP Request
▸ Show code
// Code node — runs after conversations.history HTTP Request
// Input: HTTP response from Slack API
// Output: structured task object for Notion

... expand to see full code

// Code node — runs after conversations.history HTTP Request
// Input: HTTP response from Slack API
// Output: structured task object for Notion

const items = $input.all();
const results = [];

for (const item of items) {
  const response = item.json;
  
  // Guard: API call failed
  if (!response.ok || !response.messages || response.messages.length === 0) {
    throw new Error(`Slack API error: ${response.error || 'No messages returned'}`);
  }

  const message = response.messages[0];
  const rawText = message.text || '(no text content)';

  // Truncate long messages — Notion title max is 2000 chars,
  // but over 100 chars makes the task list unreadable
  const taskTitle = rawText.length > 100
    ? rawText.substring(0, 97) + '...'
    : rawText;

  // Pull webhook event data from the trigger node
  const webhookData = $('Webhook').item.json.event;
  const channelId = webhookData.item.channel;
  const slackTs = webhookData.item.ts;
  const userId = webhookData.user;
  const emojiName = webhookData.reaction;

  // Convert Slack epoch timestamp to ISO date string
  // Slack ts format: '1704326400.000200' — take the integer part
  const epochSeconds = parseInt(slackTs.split('.')[0], 10);
  const createdDate = new Date(epochSeconds * 1000).toISOString().split('T')[0];

  // Build Slack deep link URL
  // Format: /archives/CHANNEL_ID/pTIMESTAMP (no dot in timestamp)
  const tsForUrl = slackTs.replace('.', '');
  const workspaceDomain = 'yourworkspace'; // replace with your Slack subdomain
  const messageUrl = `https://${workspaceDomain}.slack.com/archives/${channelId}/p${tsForUrl}`;

  results.push({
    json: {
      taskTitle,
      slackUser: userId,
      channelId,
      slackTimestamp: slackTs,
      messageUrl,
      createdDate,
      emojiUsed: emojiName,
      rawMessageText: rawText
    }
  });
}

return results;
2

n8n > New Workflow > + Add Node > Webhook

Set up the Slack Webhook node in n8n

Open your n8n instance and create a new workflow. Add a 'Webhook' node as the first node — not the Slack trigger node, because Slack's Event API requires a URL challenge verification that the generic Webhook node handles better. Set the HTTP method to POST and copy the generated webhook URL. You'll paste this into Slack's Event Subscriptions page next.

  1. 1Click '+ Add first step' in the center of the canvas
  2. 2Search for 'Webhook' and select it
  3. 3Set HTTP Method to 'POST'
  4. 4Set Response Mode to 'Using Respond to Webhook Node' — you'll add that node later
  5. 5Click 'Listen for Test Event' to activate the URL
  6. 6Copy the Test URL shown at the top of the node panel
What you should see: You should see a green 'Listening...' indicator on the Webhook node and a URL in the format https://your-n8n-instance.com/webhook-test/[uuid].
Common mistake — Use the Test URL during setup only. Slack will send real events to the Production URL — switch to that before going live or your workflow will silently drop events.
3

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

Register the n8n webhook URL in Slack and subscribe to reaction events

Paste your n8n webhook URL into the 'Request URL' field on Slack's Event Subscriptions page. Slack immediately sends a challenge POST request — n8n must respond with the challenge value within 3 seconds or Slack rejects the URL. To handle this, add a 'Respond to Webhook' node in n8n right after the Webhook node and configure it to return the challenge. Under 'Subscribe to bot events', add the event 'reaction_added'.

  1. 1Paste your n8n Test URL into the 'Request URL' field
  2. 2In n8n, add a 'Code' node after the Webhook node temporarily to echo back the challenge — use: return [{json: {challenge: $input.first().json.challenge}}]
  3. 3Connect it to a 'Respond to Webhook' node set to 'First Incoming Item'
  4. 4Wait for Slack to show a green 'Verified' checkmark
  5. 5Under 'Subscribe to Bot Events', click 'Add Bot User Event'
  6. 6Search for and add 'reaction_added'
What you should see: Slack displays a green 'Verified' badge next to your Request URL, and 'reaction_added' appears in the bot events list.
Common mistake — The challenge verification only works when your n8n workflow is active and listening. If n8n is sleeping or the workflow is inactive, Slack will reject the URL and you'll see 'Your URL didn't respond with the value of the challenge parameter'.
4

api.slack.com/apps > Your App > OAuth & Permissions > Bot Token Scopes

Add required OAuth scopes and install the app

Navigate to 'OAuth & Permissions' in your Slack app settings. Under 'Bot Token Scopes', add three scopes: channels:history (to fetch message content from the reacted message), reactions:read (to receive reaction events), and channels:read (to resolve channel names). After adding scopes, click 'Install to Workspace' and authorize the app. Copy the Bot User OAuth Token — you'll need it in n8n.

  1. 1Click 'OAuth & Permissions' in the left sidebar
  2. 2Under 'Bot Token Scopes', click 'Add an OAuth Scope'
  3. 3Add 'channels:history'
  4. 4Add 'reactions:read'
  5. 5Add 'channels:read'
  6. 6Scroll up and click 'Install to Workspace'
  7. 7Click 'Allow' on the authorization screen
  8. 8Copy the 'Bot User OAuth Token' (starts with xoxb-)
What you should see: You should see a Bot User OAuth Token starting with 'xoxb-' on the OAuth & Permissions page, and the app should now appear in your Slack workspace's Apps section.
Common mistake — channels:history only gives access to public channels the bot has been invited to. For private channels, you need groups:history instead — and you must manually invite the bot to each private channel with /invite @YourBotName.
5

n8n Workflow Canvas > + Add Node > IF

Filter for the target emoji reaction

The reaction_added event fires for every reaction in every channel the bot can see — thumbs up, heart, everything. Add an 'IF' node in n8n to filter down to just your chosen emoji. The reaction name in Slack's API payload does not include colons — ':clipboard:' is sent as 'clipboard'. Wire the IF node so only matching reactions continue to the next step.

  1. 1Click the '+' connector after the Webhook node
  2. 2Search for 'IF' and add it
  3. 3Set Condition to 'String'
  4. 4Set Value 1 to expression: {{ $json.event.reaction }}
  5. 5Set Operation to 'Equal'
  6. 6Set Value 2 to your chosen emoji name without colons, e.g. clipboard
  7. 7Leave the 'False' branch unconnected or connect it to a 'No Operation' node
What you should see: The IF node shows two output branches: 'true' for your target emoji and 'false' for everything else. Only messages reacted with 'clipboard' (or whichever emoji you chose) will proceed.
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
Notion
NO
notified
6

n8n Workflow Canvas > + Add Node > HTTP Request

Fetch the full Slack message content

The reaction_added event only gives you the message timestamp and channel ID — not the message text. You need to call Slack's conversations.history API to retrieve the actual message. Add an 'HTTP Request' node and configure it to call https://slack.com/api/conversations.history with the channel and timestamp parameters from the webhook payload. Include your Bot User OAuth Token as a Bearer token in the Authorization header.

  1. 1Add an 'HTTP Request' node after the IF node's true branch
  2. 2Set Method to 'GET'
  3. 3Set URL to 'https://slack.com/api/conversations.history'
  4. 4Under 'Query Parameters', add 'channel' = {{ $('Webhook').item.json.event.item.channel }}
  5. 5Add 'latest' = {{ $('Webhook').item.json.event.item.ts }}
  6. 6Add 'limit' = 1
  7. 7Add 'inclusive' = true
  8. 8Under 'Authentication', select 'Header Auth' and add Authorization: Bearer xoxb-your-token
What you should see: The HTTP Request node returns a JSON response with an 'ok: true' field and a 'messages' array containing the full text of the reacted message.
Common mistake — Slack returns timestamps as epoch strings like '1704067200.000100'. The conversations.history API requires both 'latest' and 'inclusive=true' to return the exact message — without 'inclusive', it returns messages before that timestamp, not the message itself.
7

n8n Workflow Canvas > + Add Node > Code

Parse the message and build the task payload

Add a 'Code' node to extract the message text, sender user ID, channel, and timestamp from the API response. You'll also format the Slack timestamp into a readable date here. The Code node lets you structure exactly the fields you want to send to Notion so the Notion node configuration stays clean.

  1. 1Add a 'Code' node after the HTTP Request node
  2. 2Set Language to 'JavaScript'
  3. 3Paste the transformation code from the Pro Tip section below
  4. 4Click 'Test Step' to verify the output fields look correct
What you should see: The Code node outputs a single item with fields: taskTitle, slackUser, channelId, slackTimestamp, messageUrl, and createdDate — all ready to map into Notion.
Common mistake — Map fields using the variable picker — don't type field names manually. Hand-typed variable names often have invisible spacing errors that produce blank output.
8

n8n Workflow Canvas > + Add Node > Notion > Create Database Page

Connect Notion and create the database entry

Add a 'Notion' node and connect it with your Notion credentials (OAuth or Internal Integration Token). Set the operation to 'Create' and resource to 'Database Page'. Select your target Notion database from the dropdown — n8n will load all your accessible databases. Map each field from the Code node output to the corresponding Notion database property. At minimum, map the task title to the database's title property.

  1. 1Add a 'Notion' node after the Code node
  2. 2Click 'Credential for Notion API' and add your Internal Integration Token
  3. 3Set Resource to 'Database Page'
  4. 4Set Operation to 'Create'
  5. 5In the Database ID field, select your task database from the dropdown
  6. 6Click 'Add Property' for each field and map: Title = {{ $json.taskTitle }}, Status = 'To Do', Source = 'Slack'
  7. 7Add a 'Text' property for Slack Message URL mapped to {{ $json.messageUrl }}
What you should see: When you test this node, a new page appears in your Notion database with the task title populated and Status set to 'To Do'.
Common mistake — n8n's Notion node only shows databases that your integration has been explicitly shared with. If your database doesn't appear in the dropdown, open Notion, click the three-dot menu on the database, go to 'Connections', and add your integration.
9

n8n Workflow Canvas > + Add Node > HTTP Request

Add a Slack confirmation reaction back to the message

Add an HTTP Request node to post a reaction back to the original Slack message confirming the task was created. This closes the loop for the person who added the emoji — they see a ✅ appear on the message within seconds and know it was captured. Call the reactions.add API endpoint with the channel, timestamp, and your confirmation emoji.

  1. 1Add a second 'HTTP Request' node after the Notion node
  2. 2Set Method to 'POST'
  3. 3Set URL to 'https://slack.com/api/reactions.add'
  4. 4Set Body Content Type to 'JSON'
  5. 5Set Body to: { "channel": "{{ $('Code').item.json.channelId }}", "timestamp": "{{ $('Code').item.json.slackTimestamp }}", "name": "white_check_mark" }
  6. 6Use the same Bearer token Authorization header as in Step 6
What you should see: After the workflow runs, a ✅ emoji appears on the original Slack message automatically.
Common mistake — You need the reactions:write scope on your Slack bot token for this step. If you didn't add it in Step 4, go back to api.slack.com, add the scope, and reinstall the app — the token changes on reinstall.
message template
🔔 New Record: {{text}} {{user}}
channel: {{channel}}
ts: {{ts}}
#sales
🔔 New Record: Jane Smith
Company: Acme Corp
10

n8n Workflow Canvas > IF Node > Respond to Webhook

Handle the Slack URL verification challenge permanently

Replace the temporary challenge-echo Code node from Step 3 with a proper IF node that detects whether the incoming request is a challenge verification or a real event. Slack sends challenge requests during URL registration and occasionally for verification checks. Route challenge requests to a Respond to Webhook node that returns the challenge value, and route real events through your main workflow logic.

  1. 1Add an IF node immediately after the Webhook trigger node
  2. 2Set Condition: check if {{ $json.type }} equals 'url_verification'
  3. 3On the 'true' branch, add a 'Respond to Webhook' node
  4. 4Set its Response Body to: { "challenge": "{{ $json.challenge }}" }
  5. 5On the 'false' branch, wire in your existing reaction filter IF node from Step 5
  6. 6Delete the temporary Code node you added in Step 3
What you should see: Your workflow now has two branches at the top: one for URL verification challenges and one for real reaction events. Both work independently.
Common mistake — Copy the webhook URL carefully — it expires if you regenerate it, and any scenarios using the old URL will silently stop working.
11

n8n Workflow > Activate Toggle | api.slack.com > Event Subscriptions > Request URL

Switch to Production URL and activate the workflow

Go back to your Webhook trigger node and copy the Production URL (not the Test URL). Update the Request URL in Slack's Event Subscriptions page with this production URL. Slack will re-verify it — your workflow must be active for this to succeed. Click 'Active' toggle at the top of your n8n workflow to turn it on, then go to Slack and react to any message with your target emoji to do a live test.

  1. 1In n8n, click 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 your Production URL
  4. 4Wait for the green 'Verified' checkmark
  5. 5In n8n, click the 'Inactive' toggle in the top right to set it to 'Active'
  6. 6Go to Slack, find any message, and react with your chosen emoji
  7. 7Check your Notion database — the task should appear within 10 seconds
What you should see: A new page appears in your Notion database with the task title, a ✅ reaction appears on the Slack message, and n8n's execution log shows a successful run with green nodes.
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 self-hosts and needs full control over the workflow logic or if you're already running n8n for other automations. The Code node in Step 7 is where n8n earns its place here — you can parse Slack's epoch timestamps, clean message text, handle edge cases like file attachments, and build deep-link URLs in one place with real JavaScript. The other reason to pick n8n: you can extend this workflow significantly without hitting per-task pricing. If you need this running for a 50-person team reacting to 200+ messages per month, n8n's self-hosted option costs you nothing per execution. The one scenario where you'd skip n8n: if nobody on your team has touched a workflow builder before and you need this working in 30 minutes. Zapier's Slack + Notion path has fewer steps to configure.

Cost

On n8n Cloud, this workflow costs roughly 5 executions per triggered reaction — the webhook receipt, the IF filter, the HTTP Request to Slack, the Code node, and the Notion create. At 200 reactions per month that's 1,000 executions. n8n Cloud's Starter plan gives you 5,000 executions per month for $20. Self-hosted n8n is free for unlimited executions if you run it on something like a $6/month DigitalOcean droplet. Zapier would run the same workflow at $19.99/month (Starter, 750 tasks) and hits its cap around 150 reactions/month. Make handles 1,000 operations for free, but Slack's reaction_added trigger requires a paid Make plan at $9/month — still cheaper than Zapier, but more expensive than self-hosted n8n.

Tradeoffs

Make's Slack module has a native 'Watch Reactions' trigger that handles the webhook verification automatically — no manual challenge-response setup. That's genuinely easier than n8n's manual webhook approach in Steps 2-3. Zapier has a 'New Reaction Added' Slack trigger that works out of the box in about 8 minutes with zero configuration. Power Automate has a Slack connector but it doesn't support reaction events at all — you'd need a workaround via a custom webhook. Pipedream has a pre-built Slack event source that handles scopes and verification for you, and its Node.js steps are comparable to n8n's Code node. n8n is still the right call if you're self-hosting or need the Code node's flexibility for message parsing — but if setup friction is your primary concern, Make's native trigger wins on ease.

Three things you'll hit after going live. First: Slack encodes special characters in message text — ampersands become &amp;, less-than signs become &lt;. Add a text cleanup step in your Code node using .replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>') before setting taskTitle. Second: Slack's retry behavior. If your n8n workflow takes more than 3 seconds to respond (common when Notion is slow), Slack sends the event again with an X-Slack-Retry-Num header — and you get duplicate tasks. Respond to Slack immediately with a 200, then do the processing. Third: Notion's API rate limit is 3 requests per second per integration. At normal team usage this never matters, but if you're bulk-testing the workflow by rapidly adding reactions, you'll see 429 errors from the Notion node. n8n doesn't automatically retry on 429 — add a Wait node with a 1-second delay before the Notion step if you're stress-testing.

Ideas for what to build next

  • Resolve Slack User IDs to Real NamesAdd an HTTP Request node between the Code node and Notion node to call Slack's users.info API with the user ID, then map the display_name into a Notion Person or Text property so tasks show who requested them instead of U04KXYZ123.
  • Support Multiple Emoji Triggers for Task TypesReplace the single emoji IF filter with a Switch node that routes 📋 to 'Task', 🐛 to 'Bug', and ❓ to 'Question' — each branch creates a Notion page with a different Type property value, so your database is pre-categorized without anyone doing manual triage.
  • Send a Notion Page Link Back to SlackAfter creating the Notion page, the API returns the new page URL — post it as a threaded reply on the original Slack message using chat.postMessage so the team can click directly into the Notion task from the conversation.

Related guides

Was this guide helpful?
Slack + Notion overviewn8n profile →