Find and alert on stale Pipedrive deals using n8n

medium complexityCost: $0-24/moRecommended

Prerequisites

Prerequisites
  • n8n instance (cloud or self-hosted)
  • Pipedrive account with API access (Growth plan or higher)
  • Pipedrive API token (from Settings > Personal > Tools and Integrations > API)
  • Slack app with Bot Token (chat:write scope) or Slack credential configured in n8n
  • n8n credentials configured for both Pipedrive and Slack

Why n8n?

n8n is the best fit for this recipe because it combines a Schedule Trigger with a Code node — exactly what you need to scan an entire pipeline on a daily basis, filter for staleness, and group results by rep. Pipedrive's native Workflow Automation can trigger on date fields (like expected close date), but it cannot query the full pipeline, calculate days since last activity, or send a consolidated per-rep summary. n8n does all three in a single workflow.

The Code node is where the real value is. You write a few lines of JavaScript to calculate staleness thresholds, group deals by owner, and format the output — all within the visual editor. No separate script to deploy, no cron job to maintain. Self-hosted n8n is free with unlimited executions, so a daily pipeline scan costs nothing to run.

How it works

  • Schedule Trigger fires once per day (e.g., 9 AM before standups)
  • HTTP Request node fetches all open deals from the Pipedrive API
  • Code node filters deals where update_time is older than the staleness threshold (default 30 days) and groups stale deals by owner_id
  • HTTP Request node fetches user details from Pipedrive to resolve owner names
  • Code node merges owner names into the grouped results and formats the Slack message
  • Slack node sends a consolidated alert per rep listing their stale deals
Schedule Trigger — not Pipedrive Trigger

This recipe uses a Schedule Trigger (daily cron), not the Pipedrive Trigger node. The Pipedrive Trigger fires on individual deal updates via webhook — useful for reacting to events in real time. But stale deal detection requires scanning the full pipeline for inactivity, which is a batch operation. A daily schedule is the right pattern here.

Step 1: Configure Pipedrive credentials in n8n

  1. In Pipedrive, go to Settings > Personal > Tools and Integrations > API
  2. Copy your API token
  3. In n8n, go to Credentials > New > Pipedrive API
  4. Paste the API token and save
  5. Note your Pipedrive company domain (the subdomain from your URL, e.g., yourcompany from yourcompany.pipedrive.com) — you will use it in API URLs

Step 2: Add a Schedule Trigger

Create a new workflow and add a Schedule Trigger node:

  • Trigger interval: Days
  • Trigger at hour: 9 (run each morning before standups)
  • Trigger at minute: 0

This fires the workflow once per day at 9:00 AM. Adjust the hour to match your team's timezone and standup schedule.

Step 3: Fetch all open deals from Pipedrive

Add an HTTP Request node to pull every open deal:

  • Method: GET
  • URL: https://api.pipedrive.com/v1/deals
  • Query Parameters:
    • status = open
    • start = 0
    • limit = 500
    • api_token = your Pipedrive API token (or use credential-based auth)

The response includes update_time, owner_id, title, value, id, and org_name for each deal — everything needed to detect staleness and build the alert.

Pagination for large pipelines

The Pipedrive API returns a maximum of 500 deals per request. If your pipeline has more than 500 open deals, check the additional_data.pagination.more_items_in_collection field in the response. If true, make another request with start set to the value of additional_data.pagination.next_start. Use a Loop node or handle pagination in the Code node with multiple HTTP requests.

Step 4: Filter stale deals and group by owner

Add a Code node to find deals with no activity in the last 30 days and group them by owner:

const response = $('Fetch Open Deals').first().json;
const deals = response.data || [];
 
const now = new Date();
const thresholdDays = 30;
const threshold = new Date(now - thresholdDays * 24 * 60 * 60 * 1000);
 
const staleDeals = deals.filter(deal => {
  const lastUpdate = new Date(deal.update_time);
  return lastUpdate < threshold;
});
 
if (staleDeals.length === 0) {
  return []; // No stale deals — workflow stops here
}
 
const byOwner = {};
for (const deal of staleDeals) {
  const ownerId = deal.owner_id || 'unassigned';
  if (!byOwner[ownerId]) {
    byOwner[ownerId] = { owner_id: ownerId, deals: [], totalValue: 0 };
  }
 
  const daysStale = Math.round((now - new Date(deal.update_time)) / 86400000);
 
  byOwner[ownerId].deals.push({
    name: deal.title,
    value: deal.value || 0,
    currency: deal.currency || 'USD',
    daysStale,
    dealId: deal.id,
    orgName: deal.org_name || 'No organization',
  });
  byOwner[ownerId].totalValue += (deal.value || 0);
}
 
return Object.values(byOwner).map(group => ({
  json: {
    owner_id: group.owner_id,
    deals: group.deals.sort((a, b) => b.daysStale - a.daysStale),
    totalValue: group.totalValue,
    count: group.deals.length,
  }
}));

This returns one item per owner, each containing their stale deals sorted by staleness (worst first). If no deals are stale, it returns an empty array and the workflow stops — no unnecessary Slack messages.

Step 5: Fetch user details for owner names

Add another HTTP Request node to get Pipedrive user details:

  • Method: GET
  • URL: https://api.pipedrive.com/v1/users
  • Query Parameters:
    • api_token = your Pipedrive API token

This returns all users in your Pipedrive account with their id and name. You will map these to the owner_id values from the previous step.

Step 6: Merge owner names and format messages

Add a second Code node to combine owner names with the grouped stale deals:

const ownerGroups = $('Filter Stale Deals').all();
const usersResponse = $('Fetch Users').first().json;
const users = usersResponse.data || [];
 
const userMap = {};
for (const user of users) {
  userMap[user.id] = user.name;
}
 
const companyDomain = 'yourcompany'; // Replace with your Pipedrive subdomain
 
return ownerGroups.map(item => {
  const group = item.json;
  const ownerName = userMap[group.owner_id] || 'Unassigned';
 
  const dealLines = group.deals.map((d, i) =>
    `${i + 1}. *${d.name}* ($${d.value.toLocaleString()}) — ${d.daysStale} days stale`
  ).join('\n');
 
  const pipelineUrl = `https://${companyDomain}.pipedrive.com/pipeline`;
 
  const message = `⚠️ *Stale Deal Alert*\n${ownerName}, you have *${group.count} deal${group.count > 1 ? 's' : ''}* with no activity in the last 30 days:\n\n${dealLines}\n\nUpdate these deals or close them to keep the pipeline accurate.\n<${pipelineUrl}|View your pipeline in Pipedrive →>`;
 
  return {
    json: {
      owner_id: group.owner_id,
      ownerName,
      count: group.count,
      totalValue: group.totalValue,
      message,
    }
  };
});

Replace yourcompany with your actual Pipedrive subdomain. Each item in the output contains a pre-formatted Slack message for one rep.

Step 7: Send Slack alerts

Add a Slack node — it will execute once per owner from the Code node output:

  • Resource: Message
  • Operation: Send
  • Channel: #sales-hygiene (or your preferred pipeline channel)
  • Message Type: Block Kit
{
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "{{ $json.message }}"
      }
    }
  ]
}

To DM individual reps instead of posting to a shared channel, map Pipedrive owner_id values to Slack user IDs and set the channel to the Slack user ID (e.g., U01ABC123). Store the mapping in a Set node or Google Sheet.

Step 8: Activate

  1. Click Execute Workflow to test — verify stale deals are detected and Slack messages are formatted correctly
  2. Check that each owner gets a separate message with their deals listed
  3. Toggle the workflow to Active for daily execution

Troubleshooting

Common questions

Does each stale deal count as a separate n8n execution?

No. The entire workflow — schedule trigger, API calls, code processing, and all Slack messages — counts as a single execution. Even if you have 50 stale deals across 10 reps, it is 1 execution. On n8n Cloud Starter (2,500 executions/month), a daily scan uses roughly 30 executions per month.

Can I set different staleness thresholds per pipeline stage?

Yes. In the Code node, replace the single thresholdDays variable with a lookup object that maps stage IDs to thresholds. Fetch stage data from GET /v1/stages and use each deal's stage_id to apply the correct threshold — for example, 14 days for Discovery, 30 days for Proposal, and 45 days for Negotiation.

How does this compare to Pipedrive's built-in deal rotting feature?

Pipedrive's rotting feature marks deals with a red visual indicator in the pipeline view when they exceed a per-stage inactivity period. But rotting is only a visual cue — it does not trigger webhooks, send Slack alerts, or fire Workflow Automations. This n8n workflow adds the automated alert layer on top, ensuring reps are actively notified rather than needing to check the pipeline view themselves.

Cost

  • n8n Cloud Starter: $24/mo for 2,500 executions. This workflow uses 1 execution per day (~30/month).
  • Self-hosted: Free. Unlimited executions. No public URL required since this workflow uses a Schedule Trigger (outbound API calls only), not a webhook trigger.

Looking to scale your AI operations?

We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.