RevOps · Sales funnel

Pipeline view (funnel)

How many deals enter each stage, how many move on and how long they stay, rebuilt from the CRM history and not from today's snapshot.

Tested with an agentOct 2026

UsageWidely used
EffortMedium
ForSales and RevOps teams that need to know where the funnel gets stuck, with one number every dashboard repeats.
Source

CRM deals, pipelines and stage history

Rule

Each stage visit becomes a row with entry and exit

Outcome

Entries, conversion and time per stage

Activation

Dashboard, pipeline review and AI agent

What it solves

  • Each stage's conversion comes from the deal's real path, not from the stage it sits in today.
  • The bottleneck shows on two axes: where most deals are lost and where they wait the longest.
  • Every dashboard and the AI agent count stage, win and loss the same way.

Business questions it answers

Ask your agent these questions once the case is live.

At which stage did we lose the most deals this quarter?
How long does a deal take, on average, from proposal to won?
How many deals entered negotiation in September and how many have already moved on?
Did the meeting to proposal conversion drop compared with last quarter?
Which stage holds the deals that have been waiting the longest?

What you need

  • CRM connected to Nekt with stage change history (or a daily snapshot of stages saved from now on)
  • The list of pipelines that are sales funnels, the official stage order and which stages are side stages (no-show, paused, nurturing)

What you get

  • A table with one row per deal visit to a stage, with entry, exit and duration
  • Monthly funnel by pipeline and stage, with zero where nothing moved
  • Dashboard of conversion and time per stage
  • An AI agent that answers funnel questions with the right definitions

Where to activate

Pipeline review

The month's funnel with conversion and time per stage, and the bottleneck highlighted.

AI agent via MCP

Answers where the funnel loses deals and how long each stage takes, saying whether the reading is cohort or production.

Alerts

Notice when a stage's conversion falls below its last three months average.

Ready to build?

The rules are settled, but every CRM stores history differently. If yours does not, start a daily snapshot of stages today. The detailed view has the business rules, the pitfalls and the acceptance test. Or send everything straight to your agent with the Nekt MCP.

The problem

The CRM shows each deal only in the stage it is in right now. Counting the funnel from that snapshot misses every deal that passed through a stage and moved on, or was lost, and counting all pipelines together mixes sales with customer records. The result is dashboards with different conversions for the same stage and a bottleneck nobody can point to.

How it is usually done, and the rule

Each row is a mistake that already happened in practice.

The common wayThe rule
01
Count deals by current stage and call it a funnel.
The funnel comes from history, not from the snapshot

Build one row per deal visit to a stage, with entry and exit dates, from the CRM history: propertiesWithHistory.dealstage in HubSpot (it comes newest record first, so sort by date), deal_flow in Pipedrive, OpportunityHistory in Salesforce, status change events in Kommo, stage_history in Bitrix24, firstTimeIn per phase in Pipefy. If the CRM keeps no history, save a daily snapshot of stages from today or capture changes by webhook. In HubSpot, hs_v2_date_entered_<stageId> keeps only the last entry into the stage: it cannot be used as the first entry or to build visits.

02
Add up every pipeline in the CRM.
Only acquisition pipelines enter the funnel

Classify every pipeline before counting: acquisition funnel, customer records, operations queue, portfolio, post-sale, recruiting or events. Only acquisition funnels enter conversion. In a real case, counting a customer pipeline together with sales produced a false win rate of about 91%. Also exclude archived and draft pipelines. A partner pipeline, or any pipeline with more than one won stage, stays out until someone declares which stage is the win.

03
Require the deal to have gone through every stage.
Reaching a stage counts even if it was skipped

A deal reached stage N if it entered N or any later stage of the same funnel. A deal that skipped a stage counts in the skipped stage, with the date of the first later stage. A lead created in a stage by automation shows up in history only with its exit: use the exit date as the entry. The order is the official one, declared by the team: side stages (no-show, paused, nurturing) are not progress and stay out of the order. The CRM display order is only a starting point, and stage probability does not always rise with position.

04
Order stages by position and treat Lost as the last one.
Lost is not progress

The loss stage does not move a deal forward. The loss counts at the last active stage before it. Status comes from the CRM semantics (won, lost, open), never from the stage name, and a stage named after a sales rep is not a stage. In a real case, treating the move to Lost as a step forward created more than a thousand false progressions. A loss that comes straight from another pipeline, or a deal created already lost, has no active stage in the funnel: count it separately as a loss with no stage, not at the first stage.

05
Filter open stages by metadata.isClosed = false.
Closed and won come from semantics, not metadata

In HubSpot, the stages' metadata.isClosed arrives as true for all of them through the connector, including open ones. Derive closed and won from the stage probability (1.0 won, 0.0 lost) or from hs_is_closed_won and hs_is_closed on the deal. In Bitrix24, use stage_semantic_id; in Kommo, statuses 142 (won) and 143 (lost) apply to any funnel.

06
Take the latest date of each stage in the raw history.
A new visit only when the stage changes

History holds repeated records of the same stage with no real change. Mark the start of a visit only when the stage differs from the previous record of the same deal; otherwise the last entry date gets inflated. For the funnel, use the first entry into each stage; for time, add up all visits.

07
Conversion = deals won in the month / deals created in the month.
Cohort and production do not mix

Cohort funnel: start from deals created in the month and follow how far each one got. Monthly production: each stage counted in the month of its own event. Both readings are valid, but never filter the cohort by the date of a later stage, and never add creation month to close month.

08
Average days in stage mixing completed and open visits.
Time in an open stage stays separate

For a deal still open, time in the current stage is measured up to today and grows every day. Show time in completed stages separately from the age of deals still in the stage, or the average rises on its own. Time is only measured in open stages: time sitting in Won or Lost is not funnel time. Convert dates to America/Sao_Paulo only once: Bitrix24 stores in the server time zone, other CRMs in UTC.

09
Count wins by the deals sitting in the Won stage today.
A deal moved to another pipeline does not vanish

A deal moved to another pipeline leaves the original funnel and changes the past if the count uses the current pipeline. Assign each visit to the pipeline of the stage where it happened. In many CRMs, won deals are moved to an active customers pipeline right after: a win counts by the visit to the won stage, not by the current stage.

10
Silently drop visits whose stage id is no longer in the pipeline.
A deleted stage is still in the history

History keeps ids of stages that were deleted or recreated in the CRM and no longer appear in the pipeline's stage list. Without a mapping, those visits drop out of the funnel or end up with no pipeline. Map each old stage to its current equivalent, or declare that it stays out, and check that no visit is left without a stage.

Where history and status come from in each CRM

History builds the visits; status tells whether the deal was won, lost or is still open.

HubSpotHistory in propertiesWithHistory.dealstage, newest first; hs_v2_date_entered keeps only the last entrydealstage history · probability 1.0 won · 0.0 lost
PipedriveHistory in deal_flow; deal statusstatus = won | lost | open
SalesforceHistory in OpportunityHistory; closed and won on the opportunityIsClosed · IsWon
Bitrix24History in stage_history; stage semanticsstage_semantic_id = P | S | F
KommoHistory in status change events; global won and lost idsstatus 142 won · 143 lost
RD Station CRMNative status; paused counts as openwon | lost | ongoing | paused
PipefyDate per phase; if the card went back to Lost, the last time countsfirstTimeIn · lastTimeIn

Definitions

Stage entry
a deal that entered the stage, or a later stage of the same funnel, for the first time; counted once per deal, in the month of entry
Stage conversion
deals that reached the next stage / deals that entered the stage, within the same cohort
Stage loss
lost deals whose last active stage before the loss was this one, in the same pipeline; a loss coming from another pipeline or from a deal created lost is a loss with no stage
Time in stage
exit - entry of each completed visit to an open stage, in days; sum of visits when the deal returns to the stage; Won and Lost have no time
Age in current stage
today - entry into the current stage, for open deals only
Win rate
won / (won + lost) among the cohort's closed deals; open deals stay out of the denominator
Cycle
won date - creation date, for won deals only
Bottleneck
the stage with the lowest conversion and the stage with the highest median time, shown side by side

Pitfalls that already happened

False wins from a records pipeline

An active customers pipeline counted together with sales pushed the win rate close to 91%.

Classify pipelines before any calculation.

Zero stages disappear

Stages with no deals in the month vanished from the output and the funnel had missing steps.

Build the grid of all stages and fill it with zero.

Wrong date used as entry

Using the deal's update date as the entry reset the time with every field edit. Using HubSpot's hs_v2_date_entered_<stageId>, which keeps only the last entry, pushed deals that returned to a stage into the wrong month.

Use the first entry into the stage from the stage history (in HubSpot, propertiesWithHistory.dealstage).

Test, import and duplicates at the top

Integration test deals, imported lists and cards lost as duplicates inflated entries.

Exclude by loss reason and by import source, with the list declared. In HubSpot: a duplicate or invalid lead closed_lost_reason, hs_was_imported on the deal and sourceType IMPORT in the stage history.

Definition stuck in the BI tool

The MQL definition lived in a BI measure and two dashboards showed different numbers in the same leadership meeting.

Define stage and metric once, in the transformation, and have the dashboards read from it.

History that starts late

A stage history only started recording months after the CRM, and the earlier months held about a third of the real changes.

Declare from when the funnel is reliable and do not compare before that.

Validated on real data. In Nekt's own HubSpot account, every stage of every pipeline arrives with metadata.isClosed = true, including open ones. And more than 8 in 10 deals that passed through the won stage of the sales pipeline now sit in another pipeline: counting wins by current stage would show less than a tenth of the real wins.

Acceptance test

The case is done when every item passes.

Agent package

Paste into Claude, ChatGPT or any client with the Nekt MCP connected.

Full Markdown
Sales funnel by stage
- Source: stage visits table (one row per deal x visit to a stage, with entry, exit and duration). Do not count the funnel by the deal's current stage.
- In HubSpot, visits come from propertiesWithHistory.dealstage. hs_v2_date_entered_<stageId> keeps only the last entry: never use it as the first entry.
- Only pipelines classified as acquisition funnels count, with a single won stage. Customer, portfolio, operations, partner, recruiting and events pipelines stay out.
- Reached stage N = entered N or any later stage of the same funnel. A deal that skipped a stage counts in the skipped stage. Side stages (no-show, paused) are not progress.
- A loss counts at the last active stage before the loss, in the same pipeline. Lost is not progress. A loss coming from another pipeline or from a deal created lost is a loss with no stage.
- Won and lost from the CRM semantics (in HubSpot, stage probability 1.0 or 0.0). Never from the stage name or metadata.isClosed.
- Won = having passed through the won stage, even if the deal now sits in another pipeline.
- Always say whether the reading is cohort (creation month) or production (event month). Do not mix them.
- Time in stage: completed visits to open stages only; Won and Lost have no time. Age of deals still in the stage is a different metric.
- Dates in America/Sao_Paulo.
Client exceptions:
- <client>: funnel pipelines = <list>; stage order = <list>; side stages = <list>.
- <client>: deleted stages in history = <old id> -> <current stage or out>.
- <client>: top of funnel exclusions = <loss reasons and import sources>.
- <client>: history reliable from <date>; before that, only the monthly snapshot.

Variations

By owner

The same table with the deal owner at the time of each visit, not the current owner.

CRM without history

Daily stage snapshot saved from now on, or a stage change webhook, until there are enough months.

From lead to sale

Link the lead object to the deal through conversion (in Salesforce, convertedOpportunityId) to get lead, MQL, SQL and sale in one row.