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

How to Sync Asana Sprints to Slack with n8n

Automatically post sprint start announcements, completion summaries, and blocker alerts to Slack channels when Asana project milestones change.

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 running Asana-based sprints who want Slack announcements without manually posting updates

Not ideal for

Teams using Asana's native Slack integration already — the built-in app handles basic task notifications without custom logic

Sync type

real-time

Use case type

notification

Real-World Example

💡

A 12-person product team at a B2B SaaS company runs two-week sprints in Asana. Before this workflow, the PM posted sprint kickoff and wrap-up messages to #product-sprint manually — often hours late or skipped entirely. After setup, Slack gets an automatic announcement the moment the sprint milestone is marked complete or a task is flagged as blocked, including a live capacity summary pulled from Asana custom fields.

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 — Asana cannot reach localhost
Asana Personal Access Token with access to the target project — generate at https://app.asana.com/0/developer-console
Asana project GID for the sprint project — found in the project URL: app.asana.com/0/PROJECT_GID/list
Slack app with OAuth scopes: chat:write, channels:read, and installed to the workspace — create at https://api.slack.com/apps
Target Slack channel IDs for sprint announcements and blocker alerts — retrieved via Slack API or browser URL

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Task GIDevents[0].resource.gid
Event Actionevents[0].action
Task Namedata.name
Assignee Namedata.assignee.name
Completed Flagdata.completed
Changed Fieldevents[0].change.field
Slack Channel ID
3 optional fields▸ show
Due Datedata.due_on
Story Points (Custom Field)data.custom_fields[].display_value
Team Capacity % (Custom Field)data.custom_fields[].display_value

Step-by-Step Setup

1

n8n Canvas > + Node > Trigger > Webhook

Create a new n8n workflow and add the Webhook node

Open n8n and click the orange '+ New Workflow' button in the top right. On the blank canvas, click the '+' node icon and search for 'Webhook'. Select the Webhook node — this will be your trigger. n8n generates a unique webhook URL immediately. Copy that URL; you'll paste it into Asana in the next step.

  1. 1Click '+ New Workflow' in the top right of the n8n dashboard
  2. 2Click the '+' node placeholder on the canvas
  3. 3Search for 'Webhook' in the node search bar
  4. 4Select 'Webhook' under Trigger nodes
  5. 5Copy the generated webhook URL from the node panel
What you should see: You should see a Webhook node on the canvas with a green URL displayed in the node's right panel. The URL format is https://your-n8n-instance.com/webhook/[uuid].
Common mistake — n8n has two webhook URLs: a Test URL and a Production URL. Use the Test URL during setup so you can trigger it manually. Switch to the Production URL before going live — they are different endpoints.
2

Asana Developer Console > Personal Access Tokens > Copy Token

Register the webhook in Asana

Asana doesn't have a UI-based webhook setup — you have to call their API directly. Use Asana's Webhooks API (POST to https://app.asana.com/api/1.0/webhooks) with your personal access token in the Authorization header. Set the 'resource' to your Asana project GID and the 'target' to the n8n webhook URL you copied. Asana will send a handshake request immediately to verify the endpoint.

  1. 1Go to https://app.asana.com/0/developer-console and create a Personal Access Token
  2. 2Open a terminal or Postman and POST to https://app.asana.com/api/1.0/webhooks
  3. 3Set Authorization header to: Bearer YOUR_ASANA_TOKEN
  4. 4Set body: { "data": { "resource": "YOUR_PROJECT_GID", "target": "YOUR_N8N_WEBHOOK_URL" } }
  5. 5Check the response for a 201 status and note the webhook GID
What you should see: Asana returns a 201 response with a webhook object including an 'id' and 'active: true'. In n8n, your Webhook node receives the handshake ping and shows incoming data in the test panel.
Common mistake — Asana's handshake requires your n8n webhook to respond with an X-Hook-Secret header within 10 seconds. n8n handles this automatically on the Test URL, but only if the workflow is in 'listening' mode — click 'Listen for Test Event' in n8n before registering in Asana.
3

n8n Canvas > + Node > Flow > Switch

Add a Switch node to route by event type

Asana webhooks fire for many event types — task creation, section changes, due date updates, and more. You only want three: sprint start (a section named 'In Progress' gets a task), sprint completion (milestone task marked complete), and blockers (a tag named 'Blocker' is added). Add an n8n Switch node after the Webhook node to filter these three paths. Each route will lead to a different Slack message format.

  1. 1Click '+' after the Webhook node and search for 'Switch'
  2. 2Set the Switch mode to 'Rules'
  3. 3Add Rule 1: field = {{ $json.events[0].action }}, equals 'changed', AND {{ $json.events[0].change.field }} equals 'completed'
  4. 4Add Rule 2: field = {{ $json.events[0].resource.resource_type }} equals 'tag' AND action equals 'added'
  5. 5Add Rule 3: {{ $json.events[0].change.new_value.name }} contains 'In Progress'
What you should see: The Switch node shows three output branches labeled 0, 1, and 2 on the canvas. Each branch has a green dot indicating a valid rule is configured.
Common mistake — Asana webhook payloads wrap events in an array: $json.events[0]. If your project has batch updates, multiple events fire in one payload — this workflow only processes the first event. Add a Split In Batches node before the Switch if your team bulk-updates tasks.
4

n8n Canvas > + Node > Core > HTTP Request

Fetch full task details from Asana API

The Asana webhook payload gives you minimal data — just the resource GID and event type. To build a meaningful Slack message, you need task name, assignee, due date, and custom fields like story points. Add an HTTP Request node on each branch to call GET https://app.asana.com/api/1.0/tasks/{task_gid}?opt_fields=name,assignee.name,due_on,custom_fields,completed. Connect one HTTP Request node per Switch branch.

  1. 1Click '+' on Switch output 0 and add an HTTP Request node
  2. 2Set Method to GET
  3. 3Set URL to: https://app.asana.com/api/1.0/tasks/{{ $json.events[0].resource.gid }}?opt_fields=name,assignee.name,due_on,custom_fields,completed
  4. 4Under Authentication, choose 'Header Auth', set Name to 'Authorization', Value to 'Bearer YOUR_ASANA_TOKEN'
  5. 5Duplicate this node and connect copies to Switch outputs 1 and 2
What you should see: Each HTTP Request node shows a 200 response in the output panel with a full task object including name, assignee.name, due_on, and your custom_fields array.
Common mistake — The opt_fields parameter is not optional here — without it, Asana returns a minimal stub with only the GID. custom_fields won't appear unless you explicitly request them.
5

n8n Canvas > + Node > Core > Code

Add a Code node to parse sprint data and build message payloads

Asana custom fields come back as an array of objects, each with a gid and display_value. You need to find the right custom field by name (e.g. 'Story Points', 'Capacity %') rather than by index. Add a Code node after each HTTP Request node to extract these values and construct a clean object for the Slack message. This is where n8n's code node earns its place — no other node handles Asana's custom_fields array cleanly.

  1. 1Click '+' after the HTTP Request node on the sprint completion branch
  2. 2Select the 'Code' node
  3. 3Set language to JavaScript
  4. 4Paste the transformation code from the Pro Tip section below
  5. 5Run the node and verify the output shows storyPoints, capacity, taskName, and assignee fields
What you should see: The Code node output panel shows a clean JSON object with storyPoints, capacity, taskName, assigneeName, dueOn, and a pre-formatted slackText string ready to post.
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.

This Code node runs after the Asana HTTP Request node on any branch. It extracts story points and capacity from Asana's custom_fields array (which comes back as unordered objects), computes sprint summary counts, and returns a clean payload the Slack node can reference directly. Paste it into the Code node on each branch, adjusting the custom field names to match what your Asana project uses.

JavaScript — Code Node// n8n Code Node — Asana Sprint Data Parser
▸ Show code
// n8n Code Node — Asana Sprint Data Parser
// Runs after HTTP Request node fetching full task details
// $input.all() returns array of items from upstream node

... expand to see full code

// n8n Code Node — Asana Sprint Data Parser
// Runs after HTTP Request node fetching full task details
// $input.all() returns array of items from upstream node

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

for (const item of items) {
  const task = item.json.data;

  // Asana custom_fields is an unordered array — find by name, not index
  const customFields = task.custom_fields || [];

  const storyPointsField = customFields.find(
    f => f.name === 'Story Points'
  );
  const capacityField = customFields.find(
    f => f.name === 'Capacity %'
  );

  const storyPoints = storyPointsField
    ? parseInt(storyPointsField.display_value, 10) || 0
    : 0;

  const capacity = capacityField
    ? capacityField.display_value || 'N/A'
    : 'N/A';

  // Format due date from YYYY-MM-DD to human-readable
  const rawDue = task.due_on || null;
  const formattedDue = rawDue
    ? new Date(rawDue + 'T00:00:00').toLocaleDateString('en-US', {
        month: 'short',
        day: 'numeric',
        year: 'numeric'
      })
    : 'No due date';

  // Pre-build Slack message text so the Slack node stays simple
  const isCompleted = task.completed === true;
  const emoji = isCompleted ? '✅' : '🚀';
  const status = isCompleted ? 'Sprint complete' : 'Sprint started';

  const slackText =
    `${emoji} ${status}: ${task.name}\n` +
    `Owner: ${task.assignee ? task.assignee.name : 'Unassigned'}\n` +
    `Due: ${formattedDue}\n` +
    `Story Points: ${storyPoints}\n` +
    `Team Capacity: ${capacity}`;

  results.push({
    json: {
      taskName: task.name,
      assigneeName: task.assignee ? task.assignee.name : 'Unassigned',
      dueOn: formattedDue,
      storyPoints,
      capacity,
      isCompleted,
      slackText
    }
  });
}

return results;
message template
🔔 New Record: {{text}} {{user}}
channel: {{channel}}
ts: {{ts}}
#sales
🔔 New Record: Jane Smith
Company: Acme Corp
6

n8n Canvas > + Node > Core > HTTP Request

Fetch sprint-level summary from the Asana project

For sprint completion summaries, you need project-level stats — total tasks, completed count, incomplete count. Add another HTTP Request node calling GET https://app.asana.com/api/1.0/projects/{project_gid}/tasks?opt_fields=completed,name&completed_since=now. This returns only incomplete tasks. Run a second call without the filter to get total count. Wire both calls after the Code node on the completion branch.

  1. 1Add HTTP Request node after Code node on the 'completed' branch
  2. 2GET https://app.asana.com/api/1.0/projects/YOUR_PROJECT_GID/tasks?opt_fields=completed,name
  3. 3Add a second HTTP Request node with the same URL plus &completed_since=now to fetch only incomplete tasks
  4. 4Connect both nodes into a Merge node set to 'Combine > Merge By Position'
  5. 5Use an expression in the next Code node to calculate: total - incomplete = completed count
What you should see: The Merge node outputs two arrays — one with all tasks, one with incomplete tasks — which your Code node uses to compute sprint velocity numbers.
Common mistake — Asana's /tasks endpoint is paginated at 100 items. If your sprint has more than 100 tasks, you'll get an 'offset' token in the response. Add an IF node to check for the token and loop with a second HTTP Request to fetch remaining pages.
7

n8n Canvas > + Node > Apps > Slack > Post Message

Configure the Slack node for sprint start announcements

Connect a Slack node to Switch branch 2 (the 'In Progress' section trigger). In n8n, search for the Slack node and select 'Post Message' as the operation. Choose the OAuth2 credential you'll set up in the credentials panel. Set the channel to your sprint channel ID (not the name — use the ID from Slack's channel settings). Build the message text using n8n expressions referencing your Code node output.

  1. 1Click '+' on the sprint start branch and search for 'Slack'
  2. 2Select operation 'Post a Message'
  3. 3Click 'Create New Credential' and authenticate with Slack OAuth2
  4. 4Set Channel to the Slack channel ID (e.g. C04XXXXXXX)
  5. 5Set Text to: 🚀 Sprint started: {{ $node['Code'].json.taskName }} | Assignee: {{ $node['Code'].json.assigneeName }} | Due: {{ $node['Code'].json.dueOn }}
What you should see: After saving and running a test, you should see the announcement message appear in your Slack channel within 5-10 seconds of the Asana trigger firing.
Common mistake — Use the Slack channel ID, not the channel name. Names can change; IDs don't. Find the channel ID by opening Slack in a browser, clicking the channel, and copying the ID from the URL: /messages/C04XXXXXXX.
8

n8n Canvas > + Node > Apps > Slack > Post Message

Configure Slack nodes for sprint completion and blocker alerts

Add two more Slack 'Post Message' nodes — one for each remaining Switch branch. For sprint completion, use Slack's Block Kit format to post a structured summary card with task counts, story points completed, and team capacity. For blocker alerts, post to a separate #blockers channel (or the same sprint channel with a 🚧 emoji prefix). Each node references its own upstream Code node output.

  1. 1Add Slack node to the 'completed' branch, operation 'Post a Message'
  2. 2Set message body using JSON Blocks field with a Block Kit section block
  3. 3Add Slack node to the 'blocker' branch, set Channel to #blockers channel ID
  4. 4Set Text to: 🚧 Blocker flagged: {{ $node['Code'].json.taskName }} | Owner: {{ $node['Code'].json.assigneeName }}
  5. 5Test each branch independently by manually sending a matching Asana event
What you should see: Three distinct Slack messages appear in the correct channels: a plain text sprint start, a Block Kit summary card for completions, and a blocker alert with assignee name.
Common mistake — Block Kit JSON must be valid or Slack returns a 400 with 'invalid_blocks'. Validate your JSON at https://api.slack.com/tools/block-kit-builder before pasting into n8n.
9

n8n Settings > Error Workflow > + New Error Workflow

Add error handling with an Error Trigger node

Sprint notifications that silently fail are worse than no automation. Add an Error Trigger workflow in n8n: go to Settings > Error Workflow and create a separate workflow that catches failures from this one. The Error Trigger node captures the error message, the node name that failed, and the execution ID. Wire it to a Slack node that posts to a #dev-alerts channel so someone knows immediately when the Asana webhook or API call breaks.

  1. 1Click the workflow name at the top of your canvas to open workflow settings
  2. 2Scroll to 'Error Workflow' and click 'Create Error Workflow'
  3. 3On the new canvas, add an Error Trigger node
  4. 4Add a Slack node after it: Text = '❌ Sprint sync failed at node: {{ $json.failedNode }} | Error: {{ $json.errorMessage }}'
  5. 5Save and link this workflow back to the main workflow's Error Workflow setting
What you should see: In your #dev-alerts Slack channel, you receive an error message within 30 seconds whenever any node in the main workflow fails — including the node name and error text.
n8n
+
click +
search apps
Slack
SL
Slack
Add error handling with an E…
Slack
SL
module added
10

n8n Canvas > Workflow Active Toggle (top right)

Activate the workflow and switch to the Production webhook URL

During testing you used the Test webhook URL. Now click the toggle in the top right of n8n to activate the workflow. Once activated, n8n switches to the Production webhook URL automatically — but Asana is still pointed at the Test URL. Go back to the Asana Webhooks API, delete the old webhook using DELETE https://app.asana.com/api/1.0/webhooks/{webhook_gid}, and re-register using the Production URL from n8n's Webhook node settings.

  1. 1Click the 'Inactive' toggle in the top right to set workflow to 'Active'
  2. 2Open the Webhook node and copy the Production URL (not the Test URL)
  3. 3DELETE the old Asana webhook via API: DELETE https://app.asana.com/api/1.0/webhooks/OLD_GID
  4. 4Re-register a new Asana webhook with the Production URL
  5. 5Trigger a real Asana event (mark a task complete) and confirm the Slack message arrives
What you should see: A Slack message appears in your sprint channel within 3-5 seconds of the Asana action. The n8n execution log shows a green 'Success' entry with the full data trail.
Common mistake — If you forget to re-register the Asana webhook with the Production URL, events fire against the Test endpoint, which only works when you manually click 'Listen for Test Event' in n8n. You'll see no Slack messages and no errors — it just silently does nothing.

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 is already self-hosting infrastructure and wants full control over the webhook logic without paying per-task fees. n8n's Code node is the real reason to pick it here — Asana's custom_fields array is genuinely messy to parse without code, and doing it in a no-code tool means building fragile workarounds. If your team has no one comfortable running JavaScript and no interest in maintaining a self-hosted instance, Make handles this workflow adequately with its HTTP module and array iterator, and costs $9/month for the Teams plan.

Cost

On n8n Cloud, this workflow costs roughly $0 in execution fees for typical sprint volumes — n8n charges by workflow executions, and a 2-week sprint with daily Asana updates might trigger 200-400 executions per month. The Starter plan at $20/month covers 2,500 executions. If you're self-hosting, your only cost is server compute, which on a $5/month VPS is effectively nothing. Compare that to Zapier, where each Zap step counts as a task: a 3-step workflow at 400 runs/month burns 1,200 tasks, hitting the $19.99/month Professional plan minimum almost immediately.

Tradeoffs

Make beats n8n for visual conditional branching — its scenario builder shows all three sprint event paths on one canvas with labeled routers, which is easier to hand off to a non-developer. Zapier has a native Asana 'Task Completed' trigger that requires zero API setup, cutting 30 minutes off initial configuration. Power Automate has a pre-built Asana connector with a 'When a task is completed' trigger, which is useful if your org is already on Microsoft 365 — no webhook registration needed. Pipedream's Asana source handles the webhook handshake automatically and gives you a typed event schema, which removes one common failure point. None of that changes the recommendation: n8n is right here because the custom_fields parsing and multi-branch routing genuinely benefit from a real code node, and the others make you fight their abstractions to get there.

Three things you'll hit after setup. First, Asana's webhook handshake is fragile — any deployment restart that takes your n8n URL offline for more than a few seconds won't break the webhook, but three consecutive failed deliveries will cause Asana to silently deactivate it. Build a weekly cron job that calls the Asana webhooks API to verify active status. Second, Slack's Block Kit layout breaks on mobile if your sprint summary text is too long — keep task name fields under 80 characters or Slack truncates them with an ellipsis that cuts off the important numbers. Third, Asana's API rate limit is 150 requests per minute per user token — if multiple sprints close simultaneously (end of quarter), parallel HTTP Request calls from multiple executions will hit 429s. Add a 500ms Wait node before each API call during the first week to see if this is a problem before it becomes one.

Ideas for what to build next

  • Add a weekly sprint digestSet up a second n8n workflow on a scheduled trigger (every Friday at 4pm) that calls the Asana projects API, counts completed vs open tasks, and posts a formatted weekly summary to the sprint channel.
  • Create a Slack slash command to query sprint status on demandAdd a second n8n webhook that receives a Slack slash command like /sprint-status, calls the Asana API for current sprint data, and responds with live capacity and task counts directly in Slack.
  • Route blockers to a Jira ticket automaticallyExtend the blocker branch to also create a Jira issue via the Jira node in n8n, linking it back to the Asana task GID so blockers are tracked in both systems without double entry.

Related guides

Was this guide helpful?
Slack + Asana overviewn8n profile →