← All articles

GTM Operations

The Renewal Discount You Granted Once Is Now Permanent

A one-time 20% discount to save a deal quietly became the customer's forever price, and it drifted to 32% while nobody was looking. Here is the discount-sunset system that recovers the margin without a renewal fight.

· 12 min read

I pulled the discount history on a book of business once and found a customer paying 32% below list. Three years earlier a rep had given them 20% to close the initial deal. The next year a different rep, staring at a renewal date with no context, matched the discount and added 5% “to be safe.” The year after, a third rep matched that. Nobody had ever decided this customer deserved a third off forever. It happened because a discount granted once has no expiry, and a renewal defaults to last year’s price.

That is the quiet margin leak in almost every subscription business, and it hits harder now that expansion is where the retention math lives. NRR sat at a median of 102% across B2B SaaS in FY2025, with the bottom quartile down at 92% (Aleph and Benchmarkit, 2026 SaaS Benchmarks, n=230). A book bleeding trapped discount is a book giving away the exact points of net retention the board is watching. The reason the discount was granted expires (the competitive threat passed, the budget freeze lifted) but the discount does not, because nothing in the system ever sunsets it.

The fix is a schedule. Watch a single trapped discount step back toward list across three renewals, small enough each year that the customer reads it as a normal price adjustment instead of a penalty. This is the shape of the whole system, and I will build every rung under it below.

Recovery, not a cliffOne trapped discount, walked back over three renewals
0%9%18%27%36%NowRenewal 1Renewal 2Renewal 3Renewal cycle
32%Nownobody decided this number
The 32%-off account, recovered on a schedule: 32% today, 20% at the next renewal, 12%, then 5%. Each step is a single-digit effective increase the customer absorbs as normal price movement. The margin comes back without a churn fight.
32%
Below list on a customer nobody decided to discount forever
102%
Median B2B SaaS NRR in FY2025 (Aleph/Benchmarkit)
$54K
ARR recovered per year on the one account in the worked example

A discount is an event, not an attribute

The root error is treating a discount as something the customer has rather than something that happened once for a reason. When a discount is an attribute, it travels with the account forever and every renewal inherits it. When it is an event, it carries a date, a reason, an approver, and an expiry, and the renewal after that expiry starts from list unless someone re-justifies it. The entire fix flows from that reframe, and it is a data-model choice before it is a pricing policy.

On the books I have run, renewals convert north of 90%, which is the part that makes this recoverable. Your customer is not shopping at renewal. The switching cost, the retraining, the integration work all sit on their side of the table, so the renewal is the moment you hold more negotiating room than at any other point with that account. A modest, well-communicated step back toward list at renewal rarely triggers a churn, and yet most teams surrender that room by defaulting to last year’s discounted number without a second thought.

Recovering discount is the cheapest expansion you will ever book

Recovering discount is mathematically identical to expansion. A customer you move from 32% off back to 15% off has grown their contract value without buying anything new, and that lands in NRR exactly like an upsell. It is also the cheapest expansion on the menu. Expanding an existing account costs about $0.80 for every $1 of new ARR, against $1.63 to acquire that dollar net-new (Aleph and Benchmarkit, 2026). Recovered discount costs less than either, because there is no product to build and no deal to source. You are collecting revenue you already earned and forgot to charge for.

Discount recovery is expansion: it lifts NRR on the book you already own

net retention

Try

Above 100% the existing book grows on its own before a single new logo signs. Below 100% you are running up a down escalator: new sales have to refill the leak before they add anything.

net retention: 110.0%

The gap between a discounted book and a recovered one shows up directly in the retention numbers investors read.

MetricValueDetailSource
NRR median102%All B2B SaaS, FY2025Aleph/Benchmarkit 2026
NRR top quartile vs bottom110% vs 92%25th pct fell 95 to 92 YoYAleph/Benchmarkit 2026
NRR usage vs seat pricing108% vs 98%10-point gap, wideningAleph/Benchmarkit 2026
GRR median84%All SaaS, FY2025Aleph/Benchmarkit 2026
Expansion cost per $1 ARR$0.80vs $1.63 to acquire net-newAleph/Benchmarkit 2026

A book sitting at 102% NRR with 8% of ARR trapped in unnecessary discount is a 108%-plus book wearing a discount it forgot to remove. Every point of discount you recover is a point of net retention you were leaving on the table, bought at the lowest cost per dollar in your entire revenue mix.

The two-line SOQL that finds the leak

You cannot sunset what you cannot see. If your quotes or opportunities carry a discount percentage, the leak is one query away. Pull every active customer whose current discount exceeds a threshold, sorted by dollars at stake, and you have your recovery target list in an afternoon.

SELECT Account.Name, Account.Id,
       Amount, List_Amount__c,
       (1 - Amount / List_Amount__c) discount_pct,
       CloseDate, Discount_Reason__c, Discount_Expiry__c
FROM Opportunity
WHERE Type = 'Renewal'
  AND StageName = 'Renewal Pipeline'
  AND List_Amount__c > 0
  AND (1 - Amount / List_Amount__c) > 0.15
ORDER BY List_Amount__c DESC

Two fields in that query probably do not exist yet in your org: Discount_Reason__c and Discount_Expiry__c. That absence is the whole problem. When there is no reason field, nobody knows why the discount exists, so nobody dares remove it. When there is no expiry field, the discount is structurally permanent. Adding those two fields and making the reason required on any discounted quote is the single change that unlocks the entire system, because it converts every discount from a silent attribute into an event with an audit trail.

The sunset schedule, not a cliff

The mistake teams make when they discover the leak is to yank the discount all at once, which does trigger churn and confirms everyone’s fear that recovery is dangerous. The right move is a scheduled step-back. A discount granted for a reason that has expired steps back toward list over two or three renewals, in increments small enough that the annual increase reads as a normal price adjustment.

RenewalCurrent discountSunset targetEffective increaseChurn risk
Year 1 (grant)20% off20% offnonenone
Year 220% off15% off~6%low
Year 315% off8% off~8%low
Year 48% off0% off~9%low-moderate

Each step is a single-digit effective increase, well inside the range customers absorb as normal SaaS price movement. Compare that to the cliff: pulling 20% in one renewal is a 25% effective increase that reads as a punishment and puts the renewal at risk. The schedule recovers the same margin over three years instead of one, and it keeps the 90% renewal conversion intact. That is the exact curve the hero chart traces, one trapped discount walked down in steps the customer never flinches at. Patience is the difference between recovery and a churn spike.

Cliff vs schedule Same margin recovered, very different renewal risk
Renewal yearEffective priceCliff: 25% jump, churn riskY1 20% offY2 15% offY3 8% offY4 list
The cliff yanks 20% in one year and spikes churn risk. The schedule steps back in single-digit increments the customer absorbs as normal price movement.

The flow that stops the silent match

Reporting on the leak is half the job. The other half is stopping new discounts from becoming permanent. The build is a before-save flow on renewal opportunities that reads last year’s discount and its expiry and refuses to silently carry it forward. If the reason has expired, the flow sets the renewal to the scheduled step-back price and flags it for the rep, so the default becomes recovery instead of matching.

Flow: Opportunity | Renewal Discount Sunset (before-save)
  Trigger: Renewal opp created or Amount updated
  Get: prior-term opp discount %, Discount_Reason__c, Discount_Expiry__c
  Decision:
    - Reason is contractual/multi-year  -> carry discount, no flag
    - Discount_Expiry__c < renewal CloseDate -> set price to next sunset step,
        set Discount_Recovery_Flag__c = true, post note to owner
    - Reason blank -> set flag, require reason before save
  Result: renewal defaults to recovery price, rep must justify to keep the discount

This inverts the default. Today the path of least resistance is matching last year’s number. After this flow, the path of least resistance is the scheduled recovery, and keeping the discount requires the rep to type a reason and, above a threshold, get an approval. You have not banned discounts. You have made permanence a deliberate choice instead of an accident. The declarative build pattern here is the same one behind the deal-desk guardrails in the required-fields tax: make the right thing the default, make the exception cost a sentence.

Here’s how I’d build it: the discount-sunset system

Five parts. The first two are a data-model change, the middle two are the recovery motion, the last one keeps it from re-leaking. Read it against the hero curve: parts one and two make the curve measurable, parts three and four walk it down, part five books the recovered margin as expansion.

The discount-sunset system
  1. 1

    Add reason and expiry to every discount

    Two fields: a required reason picklist on any discounted quote and an expiry date that defaults to the end of the current term unless the reason is contractual. No discount ships without both. This converts discounts from attributes into events.

  2. 2

    Backfill the current book from history

    Run the discount query, sort by ARR at stake, and interview owners or read notes to assign a reason and expiry to each existing discount. Painful once, and it is the map for every recovery that follows.

  3. 3

    Set a sunset schedule per discount tier

    Deep discounts (over 20%) step back over three renewals, shallow ones over two. Cap each annual step at a single-digit effective increase so it reads as normal price movement, never a penalty. This is the slope of the hero curve.

  4. 4

    Ship the before-save recovery flow

    On renewal, if the reason has expired, default the price to the next sunset step and flag it. Keeping the discount requires a typed reason and, above a threshold, deal-desk approval. Recovery becomes the default, matching becomes the exception.

  5. 5

    Report recovered margin as expansion

    Track recovered discount dollars monthly and count them in NRR, because that is what they are, and the cheapest kind at $0.80 per dollar. Put the number on the same review as new expansion so recovery gets the credit and attention it earns.

Attribute vs event

The whole system reduces to one reframe. Here is what changes when a discount stops being something the customer has and becomes something that happened.

Discount as an attribute Discount as an event
What the record stores A price, no reason, no end A price, a reason, an approver, an expiry
What the renewal defaults to Last year's discounted number The scheduled step back toward list
What a rep must do to keep it Nothing; matching is the default Type a reason, get approval above threshold
Year-over-year drift Deepens as reps pad "to be safe" Recovers on a schedule
Margin outcome 8% of ARR trapped, compounding Recovered as expansion, counted in NRR
One data-model choice decides whether your discounts are recoverable or permanent.

The date field is the whole fix

The discount that is eating your margin was granted for a reason that has almost certainly expired. The only thing keeping it alive is that nothing in your system knows the reason existed or that it ended. Add two fields, backfill the book once, schedule the recovery in single-digit steps, and let a flow make recovery the default. The renewal converts anyway, because your customer is not shopping, and the margin you have been giving away for three years comes back as expansion, at the lowest cost per dollar you will book all year.

Run the discount query this week and sort by ARR at stake. The customers at the top are paying you the least for reasons no one remembers. For the reframe that turns this from a one-off cleanup into a standing motion, read why expansion should never be accidental. Then add the reason and expiry fields, and the discount you granted once stops being permanent by accident.

renewals pricing retention

Keep reading

One email. Every week.

One email a week: an operating problem I solved or botched, with the model, the numbers, and what I would change. No roundups, no theory, unsubscribe whenever it stops being useful.

The newsletter opens soon.

Connect a provider in src/config.ts