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.
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.
net retention
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.
| Metric | Value | Detail | Source |
|---|---|---|---|
| NRR median | 102% | All B2B SaaS, FY2025 | Aleph/Benchmarkit 2026 |
| NRR top quartile vs bottom | 110% vs 92% | 25th pct fell 95 to 92 YoY | Aleph/Benchmarkit 2026 |
| NRR usage vs seat pricing | 108% vs 98% | 10-point gap, widening | Aleph/Benchmarkit 2026 |
| GRR median | 84% | All SaaS, FY2025 | Aleph/Benchmarkit 2026 |
| Expansion cost per $1 ARR | $0.80 | vs $1.63 to acquire net-new | Aleph/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.
| Renewal | Current discount | Sunset target | Effective increase | Churn risk |
|---|---|---|---|---|
| Year 1 (grant) | 20% off | 20% off | none | none |
| Year 2 | 20% off | 15% off | ~6% | low |
| Year 3 | 15% off | 8% off | ~8% | low |
| Year 4 | 8% off | 0% 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.
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.
- 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
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
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
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
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 |
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.
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