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

How to Sync Asana Sprints to Slack with Pipedream

Fires a Slack message to the relevant team channel whenever an Asana project milestone changes — sprint starts, completions, and blockers included.

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

Not ideal for

Teams managing sprints entirely inside Slack or Linear — the Asana webhook layer adds setup overhead that won't pay off without active Asana usage.

Sync type

real-time

Use case type

notification

Real-World Example

💡

A 12-person product team at a B2B SaaS company uses this to post sprint kick-off summaries, blocker flags, and completion recaps directly into #sprint-updates on Slack. Before this workflow, the Scrum Master spent 20-30 minutes each sprint boundary copying task counts, capacity notes, and due dates from Asana into Slack by hand. Now the message fires within 15 seconds of the Asana event and includes task counts, assignee capacity, and a direct link to the project.

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 Pipedream

Copy the pre-built Pipedream blueprint and paste it straight into Pipedream. 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.

Asana account with admin or project editor access on the sprint project — you need permission to register webhooks on the project
Slack workspace where you have permission to add apps and the bot can be invited to #sprint-updates and #sprint-blockers channels
Pipedream account — free tier works for under 100 executions/day; paid plan needed above that threshold
Asana OAuth scopes: openid, email, profile, tasks:read, projects:read, events — all granted by default via Pipedream's OAuth flow
Slack bot scopes: chat:write, channels:read, groups:read — required for posting to both public and private channels

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Sprint Event Typeresource.resource_type + memberships[0].section.name
Task Namename
Assignee Nameassignee.name
Section Namememberships[0].section.name
Project Namememberships[0].project.name
Slack Target Channel
Task URLpermalink_url
3 optional fields▸ show
Due Datedue_on
Story Points / Effortcustom_fields[?(@.name=='Story Points')].number_value
Blocker Flagcustom_fields[?(@.name=='Blocked')].enum_value.name

Step-by-Step Setup

1

pipedream.com > Dashboard > New Workflow

Create a new Pipedream workflow

Go to pipedream.com and sign in. Click 'New Workflow' in the top-left dashboard. You'll land on the workflow canvas with an empty trigger slot at the top. Give the workflow a name like 'Asana Sprint → Slack' using the pencil icon next to 'Untitled Workflow' at the top of the page. This name shows up in logs and is worth setting before you forget.

  1. 1Click 'New Workflow' in the left sidebar
  2. 2Click the pencil icon next to 'Untitled Workflow' and type 'Asana Sprint → Slack'
  3. 3Click the '+ Add Trigger' block at the top of the canvas
What you should see: You should see the trigger selector panel open on the right side of the canvas.
2

Workflow Canvas > Add Trigger > Asana > New Story (Instant)

Set Asana webhook as the trigger

In the trigger selector, search for 'Asana' and select it. Choose the trigger type 'New Story (Instant)' — this fires on any Asana activity event including task updates, section moves, and custom field changes, which covers sprint start, blocker, and completion events. Connect your Asana account via Connected Accounts when prompted. Pipedream generates a webhook URL immediately and registers it with Asana's Events API automatically — you don't need to touch the Asana API console.

  1. 1Type 'Asana' in the trigger search box
  2. 2Select 'New Story (Instant)' from the trigger list
  3. 3Click 'Connect Asana Account' and authenticate via OAuth
  4. 4Select your target Asana workspace from the dropdown
  5. 5Select the specific Asana project that contains your sprints
What you should see: You should see a green 'Connected' badge and a generated webhook URL under the trigger config. Pipedream will show a 'Listening for events...' status.
Common mistake — The 'New Story (Instant)' trigger fires on every story — comments, likes, and field changes included. You will filter down to sprint-relevant events in the code step. If you don't add that filter, Slack will get spammed.
Pipedream
+
click +
search apps
Slack
SL
Slack
Set Asana webhook as the tri…
Slack
SL
module added
3

Workflow Canvas > Trigger > Events Tab

Send a test event from Asana

Open Asana in a new tab and navigate to the project you selected. Move a task to a new section, or add a comment. Return to Pipedream — you should see an event appear under the trigger's 'Events' tab within 15 seconds. Click the event to expand it and inspect the raw payload. You need to confirm the shape of the data before writing transformation logic. Pay attention to the 'type' and 'resource.resource_type' fields — these tell you whether the event is a task move, a comment, or a custom field change.

  1. 1Open Asana and move a task between sections in your sprint project
  2. 2Return to Pipedream and click the 'Events' tab on the trigger block
  3. 3Click the incoming event to expand its JSON payload
  4. 4Note the values of 'type', 'resource.resource_type', and 'resource.gid'
What you should see: You should see a raw JSON payload with fields including 'type', 'created_at', 'resource', and 'user'. The 'type' field will say something like 'added' or 'changed'.
Common mistake — If no event appears after 60 seconds, the webhook registration may have failed silently. Delete the trigger step and re-add it — Asana occasionally rejects webhook registrations on first attempt without surfacing an error in Pipedream.
Pipedream
▶ Deploy & test
executed
Slack
Asana
Asana
🔔 notification
received
4

Workflow Canvas > + Add Step > Run Node.js Code

Add a Node.js step to classify the sprint event

Click '+ Add a Step' below the trigger and choose 'Run Node.js code'. This step reads the Asana event payload and classifies it as a sprint start, completion, blocker, or ignorable event. Use the $ object to access trigger output via steps.trigger.event. Write a switch or if/else block based on the task's section name or custom field value. Return a structured object — event_type, project_name, task_name, assignee, due_date — so the next step can build the Slack message cleanly.

  1. 1Click '+ Add a Step' below the trigger block
  2. 2Select 'Run Node.js code' from the step type list
  3. 3Paste your classification logic into the code editor
  4. 4Click 'Test' to run the step against the sample event you captured
What you should see: The step's 'Return Value' panel should show a JSON object with fields like event_type: 'sprint_start', task_name: 'Design Handoff', and assignee: '[email protected]'.
Common mistake — Asana's webhook payload does not include full task details — it only includes the resource GID. You'll need to make a secondary Asana API call inside this step to fetch the task's name, assignee, and section. Skip this and you'll have an empty message.
5

Workflow Canvas > Node.js Code Step > Code Editor

Fetch full task details from Asana API

Inside the same Node.js step (or a second one), use Pipedream's built-in @pipedream/platform axios or the native fetch to call Asana's Tasks API: GET https://app.asana.com/api/1.0/tasks/{task_gid}?opt_fields=name,assignee.name,due_on,memberships.section.name,custom_fields. Pass your Asana OAuth token from the connected account using auths.asana.oauth_access_token. Extract the section name to determine sprint boundary — teams typically name sections 'Sprint 12 - Active' or 'Done'.

  1. 1Add an import or require for axios at the top of the code step
  2. 2Build the API URL using the resource GID from steps.trigger.event.resource.gid
  3. 3Set the Authorization header to 'Bearer ' + auths.asana.oauth_access_token
  4. 4Parse the response and extract name, assignee.name, due_on, and memberships[0].section.name
What you should see: The step returns an object with the full task data including the section name, which you'll use to route the right message to Slack.
Common mistake — Asana rate limits the Tasks API to 1,500 requests per minute per user token. If you have a very active project with many concurrent events, you can hit this. Add a 200ms delay between rapid successive calls if you're processing batch events.
6

Workflow Canvas > + Add Step > Run Node.js Code

Add conditional logic to route by event type

Add another Node.js step to decide which Slack message template to use based on event_type. Sprint starts get a summary block with team capacity and task count. Completions get a recap with completed vs. incomplete ratio. Blockers get an urgent-formatted message with the assignee tagged. Return a message object with the text, channel name, and any Block Kit JSON you want. Keeping routing logic in its own step makes it easier to debug — you can test each branch independently.

  1. 1Click '+ Add a Step' below the previous code step
  2. 2Select 'Run Node.js code'
  3. 3Reference the classification output using steps.classify.$return_value.event_type
  4. 4Build a switch statement with cases for sprint_start, sprint_complete, and blocker
  5. 5Return a message object with fields: channel, text, blocks (optional)
What you should see: The Return Value panel shows a fully formed Slack message object with the correct channel name and message text for the event type.
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
Type
matches criteria?
yes — passes through
no — skipped
Asana
AS
notified
7

Workflow Canvas > + Add Step > Run Node.js Code

Add Asana project summary lookup for sprint start messages

Sprint start messages need aggregate data — how many tasks are in the sprint, who is assigned to what, and total story points if you use them. Add a Node.js step that calls GET https://app.asana.com/api/1.0/projects/{project_gid}/tasks?opt_fields=assignee.name,custom_fields,completed to pull all tasks in the active section. Count them, group by assignee, and sum any custom field used for story points. This data populates the capacity block in the Slack message.

  1. 1Add a new Node.js step after the routing step
  2. 2Call the Asana Projects Tasks API with the project GID from your trigger
  3. 3Filter the response to only tasks in the active sprint section
  4. 4Group tasks by assignee.name and count per person
  5. 5Return an object with total_tasks, by_assignee array, and total_points
What you should see: The step returns capacity data like: { total_tasks: 18, by_assignee: [{name: 'Luis', count: 5}, ...], total_points: 42 }.
Common mistake — If your project has more than 100 tasks, the Asana API paginates results. You'll need to loop with the offset token or your task count will be wrong. The API returns a 'next_page.offset' field when more records exist.
message template
🔔 New Record: {{text}} {{user}}
channel: {{channel}}
ts: {{ts}}
#sales
🔔 New Record: Jane Smith
Company: Acme Corp
8

Workflow Canvas > + Add Step > Slack > Send Message

Connect Slack and send the message

Click '+ Add a Step', search for 'Slack', and select the 'Send Message' action. Connect your Slack workspace via Connected Accounts. Set the Channel field to reference steps.route.$return_value.channel — this lets you dynamically route blockers to #sprint-blockers and summaries to #sprint-updates from the same workflow. Set the Text field to steps.route.$return_value.text. If you built Block Kit JSON in the routing step, paste it into the Blocks field.

  1. 1Click '+ Add a Step' and search for 'Slack'
  2. 2Select the 'Send Message' action
  3. 3Click 'Connect Slack Account' and authorize with your workspace
  4. 4Set Channel to {{steps.route.$return_value.channel}}
  5. 5Set Text to {{steps.route.$return_value.text}}
  6. 6Optionally set Blocks to {{steps.route.$return_value.blocks}} for rich formatting
What you should see: You should see a green 'Connected' badge next to your Slack workspace name. After testing, a real message should appear in the target Slack channel within 5 seconds.
Common mistake — The Slack bot must be invited to each channel it posts in. If the channel is private, you must invite the bot manually with /invite @YourBotName inside Slack. The workflow will fail silently — Slack returns a 'not_in_channel' error that Pipedream logs but doesn't surface as a workflow failure by default.
9

Workflow Canvas > Workflow Settings > Error Handling

Add error handling and dead-letter logging

Wrap your API calls in try/catch blocks and use $.flow.exit('reason') to halt execution cleanly when the event is not sprint-relevant — like a comment or a like. For genuine errors (API timeouts, auth failures), use console.error() to log context and re-throw so Pipedream marks the execution as failed. This gives you a filterable error log in the Pipedream dashboard. Set up an error notification in Pipedream's workflow settings to email or Slack yourself on failures.

  1. 1Wrap all fetch/axios calls in try/catch in each code step
  2. 2Use $.flow.exit('not a sprint event') for ignorable events
  3. 3Add console.error(error.message, error.response?.data) in catch blocks
  4. 4Click 'Settings' (gear icon) on the workflow and enable 'Send error notifications'
  5. 5Set the error notification destination to a Slack channel or email
What you should see: Non-sprint events exit cleanly and show as 'Skipped' in the executions log. Real errors show as 'Failed' with a stack trace you can inspect.
10

Workflow Canvas > Deploy > Executions Tab

Deploy and test all three event types

Click 'Deploy' in the top right to activate the workflow. In Asana, manually simulate each event type: move a task into your sprint's active section (sprint start), move one to Done (completion), and add a custom field value indicating a blocker. Check Slack after each action — messages should arrive within 15 seconds. Check the Pipedream executions log to confirm each run completed without errors and that the event_type classification matched what you expected.

  1. 1Click 'Deploy' in the top right corner of the workflow canvas
  2. 2Open Asana and move a task into the active sprint section
  3. 3Check #sprint-updates in Slack for the sprint start message
  4. 4Move a different task to your 'Done' section and confirm the completion message
  5. 5Set a blocker custom field on a task and confirm the urgent message in #sprint-blockers
What you should see: Three distinct Slack messages appear across the correct channels, each with the right format and task data. The Pipedream executions tab shows three successful runs.
Common mistake — Deploying the workflow does not replay past events. Only Asana events that fire after deployment trigger the workflow. If you want to test against historical data, use Pipedream's manual trigger option with a pasted payload.
11

Workflow Canvas > Trigger Step > Filters Tab

Set event filters to suppress noise

Go back to the trigger step and click 'Add filter'. Add a filter rule that only passes events where resource.resource_type equals 'task'. This immediately drops comment events and like events before they reach your code steps, cutting your execution count and cost. You can add additional filters — like requiring the project GID to match a specific value — if multiple Asana projects are in the same workspace and you only want sprint events from one of them.

  1. 1Click the trigger step to expand its configuration
  2. 2Click the 'Filters' tab
  3. 3Click 'Add Filter' and set field to 'resource.resource_type'
  4. 4Set operator to 'equals' and value to 'task'
  5. 5Click 'Save' and re-deploy the workflow
What you should see: The executions log shows fewer runs after adding the filter. Comment and like events from Asana no longer appear as executions in the Pipedream dashboard.
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.

Paste this into the classification and task-fetch Node.js step (step 4). It handles the secondary Asana API call to get full task details, classifies the sprint event, assembles the capacity summary for sprint starts, and returns a clean object for the routing step to consume. Replace SPRINT_SECTION_PREFIX with whatever prefix your team uses for active sprint sections — e.g., 'Sprint' or 'Q1-'.

JavaScript — Code Stepimport axios from 'axios';
▸ Show code
import axios from 'axios';
export default defineComponent({
  props: {

... expand to see full code

import axios from 'axios';

export default defineComponent({
  props: {
    asana: {
      type: 'app',
      app: 'asana',
    },
  },
  async run({ steps, $ }) {
    const event = steps.trigger.event;

    // Drop non-task events immediately
    if (event.resource?.resource_type !== 'task') {
      $.flow.exit('Not a task event — skipping');
    }

    const taskGid = event.resource.gid;
    const token = this.asana.$auth.oauth_access_token;
    const SPRINT_SECTION_PREFIX = 'Sprint';
    const BLOCKER_FIELD_NAME = 'Blocked';
    const POINTS_FIELD_NAME = 'Story Points';

    // Fetch full task details from Asana
    let task;
    try {
      const res = await axios({
        method: 'GET',
        url: `https://app.asana.com/api/1.0/tasks/${taskGid}`,
        params: {
          opt_fields: [
            'name',
            'assignee.name',
            'assignee.email',
            'due_on',
            'permalink_url',
            'memberships.section.name',
            'memberships.project.name',
            'memberships.project.gid',
            'custom_fields.name',
            'custom_fields.number_value',
            'custom_fields.enum_value.name',
          ].join(','),
        },
        headers: { Authorization: `Bearer ${token}` },
      });
      task = res.data.data;
    } catch (err) {
      console.error('Failed to fetch task:', err.response?.data || err.message);
      throw new Error(`Asana task fetch failed for GID ${taskGid}`);
    }

    const sectionName = task.memberships?.[0]?.section?.name ?? '';
    const projectName = task.memberships?.[0]?.project?.name ?? 'Unknown Project';
    const projectGid = task.memberships?.[0]?.project?.gid;

    // Classify the event
    const blockerField = task.custom_fields?.find(f => f.name === BLOCKER_FIELD_NAME);
    const isBlocked = blockerField?.enum_value?.name === 'Yes';
    const isDone = sectionName.toLowerCase().includes('done') || sectionName.toLowerCase().includes('complete');
    const isActive = sectionName.startsWith(SPRINT_SECTION_PREFIX) && !isDone;

    let eventType;
    if (isBlocked) eventType = 'blocker';
    else if (isDone) eventType = 'sprint_complete';
    else if (isActive) eventType = 'sprint_start';
    else {
      $.flow.exit(`Section '${sectionName}' is not a recognized sprint section — skipping`);
    }

    // For sprint start events, fetch aggregate capacity data
    let capacityData = null;
    if (eventType === 'sprint_start' && projectGid) {
      const allTasks = [];
      let offset = null;
      do {
        const params = {
          project: projectGid,
          opt_fields: 'assignee.name,custom_fields.name,custom_fields.number_value,completed',
          limit: 100,
        };
        if (offset) params.offset = offset;
        const pageRes = await axios({
          method: 'GET',
          url: 'https://app.asana.com/api/1.0/tasks',
          params,
          headers: { Authorization: `Bearer ${token}` },
        });
        allTasks.push(...pageRes.data.data);
        offset = pageRes.data.next_page?.offset ?? null;
      } while (offset);

      const activeTasks = allTasks.filter(t => !t.completed);
      const byAssignee = activeTasks.reduce((acc, t) => {
        const name = t.assignee?.name ?? 'Unassigned';
        acc[name] = (acc[name] ?? 0) + 1;
        return acc;
      }, {});
      const totalPoints = activeTasks.reduce((sum, t) => {
        const pf = t.custom_fields?.find(f => f.name === POINTS_FIELD_NAME);
        return sum + (pf?.number_value ?? 0);
      }, 0);

      capacityData = {
        total_tasks: activeTasks.length,
        total_points: totalPoints,
        by_assignee: Object.entries(byAssignee).map(([name, count]) => ({ name, count })),
      };
    }

    return {
      event_type: eventType,
      task_name: task.name,
      assignee_name: task.assignee?.name ?? 'Unassigned',
      assignee_email: task.assignee?.email ?? null,
      due_date: task.due_on ?? null,
      section_name: sectionName,
      project_name: projectName,
      permalink: task.permalink_url,
      capacity: capacityData,
    };
  },
});

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 Pipedream for this if your team has any developer capacity at all. The webhook from Asana fires within seconds, and the Node.js code steps give you enough control to handle Asana's two-step payload problem — webhook arrives with a GID, you fetch full task details separately — without building a separate backend. Two concrete reasons Pipedream wins here: first, the Connected Accounts system handles OAuth token refresh for both Asana and Slack automatically, which matters because Asana tokens expire. Second, you can add a code step that does the capacity aggregation (loop through all sprint tasks, group by assignee, sum story points) in the same workflow without needing a separate service. If your team doesn't write JavaScript at all, use Make instead — its Asana module handles the two-step fetch pattern with a built-in 'Get Task' module and the iterator module handles pagination without code.

Cost

Cost math is straightforward. Pipedream's free tier gives you 10,000 invocations per month. Each Asana event consumes one invocation regardless of how many code steps run. A team doing one 2-week sprint with 30 tasks and moderate activity — moves, blocker flags, completions — will generate roughly 50-150 Asana events per sprint, or 100-300 per month. That's well inside the free tier. At the paid tier ($29/month), you get 100,000 invocations. You'd need an unusually active Asana project to approach that ceiling. Make's free tier gives you 1,000 operations per month, but each step in a Make scenario counts as an operation — a 4-step scenario uses 4 operations per run. At 200 Asana events/month with 4 steps each, that's 800 operations — just under the free limit. Pipedream is cheaper for this workflow once you're past 250 events/month.

Tradeoffs

Make's Asana module is easier to configure — you pick the project from a dropdown and the 'Watch Tasks' module handles the webhook setup in one click, no payload inspection required. Zapier has an Asana trigger ('Task Changed') but it's polling-based on most plans, which means 5-15 minute delays on blocker alerts. That's a real problem when a blocker is time-sensitive. n8n's Asana node supports webhooks, and if you self-host n8n you pay $0 in platform costs, which matters for teams watching budget. Power Automate has an Asana connector but it's polling, and the flow-building UI makes conditional routing (different messages for different event types) genuinely tedious compared to a few lines of JavaScript. Pipedream is the right call here because the code flexibility and instant webhook processing are exactly what this workflow needs — not because the others are bad.

Three things you'll hit after setup. First, Asana's webhook registration fails silently roughly 15% of the time on the first attempt — no error is surfaced, the trigger just never fires. Re-registering always fixes it. Second, if your Asana project uses the native Sprint feature (available on Business plan), the section structure is different — tasks appear in sprint groups, not regular sections, and the memberships array looks different. The section name classification logic needs adjustment for this. Third, Slack's Block Kit formatting is strict — a single malformed JSON block silently falls back to plain text with no error. Build and validate your Block Kit JSON at Slack's Block Kit Builder before hardcoding it in the workflow.

Ideas for what to build next

  • Add a weekly sprint digest via scheduled triggerCreate a second Pipedream workflow with a scheduled trigger (every Friday at 4pm) that pulls all Asana tasks modified during the sprint week and posts a formatted digest to Slack — separate from the real-time event flow.
  • Sync Slack blocker replies back to AsanaAdd a reverse workflow that listens for replies in #sprint-blockers using Slack's Events API webhook, parses the thread, and adds a comment to the corresponding Asana task — so conversation stays linked to the work.
  • Extend to Google Sheets for sprint metrics historyAfter the Slack message step, add an additional step that appends sprint completion data (date, task count, points delivered, blockers) to a Google Sheet — gives you a historical record for retrospectives without manual tracking.

Related guides

Was this guide helpful?
Slack + Asana overviewPipedream profile →