A guest finds your hotel, checks your website, then sees the same room for less on another site. They book there, you pay a commission, and the guest learns that your own website is not the place to book. When that happens because a partner has leaked a rate you never meant to sell to the public, your own inventory is being used to undercut you.
Rate parity monitoring is how hotels and travel brands find out where that happens, in which markets and through which channels. This guide covers what parity means now that the rules have changed, why most parity checks raise false alarms, and tested code that compares like with like.
Key takeaways
- Parity is no longer something an online travel agency can impose on hotels in much of Europe. It is now mostly about the hotel’s own interest: not being undercut with its own rooms.
- Undercuts usually come from leaked wholesale or package rates appearing on sites the hotel never contracted with, not from the main agencies themselves.
- Prices must be checked from the markets where guests book. The same room can be shown at different prices, currencies and tax displays depending on where the shopper is.
- Compare only identical products: same dates, room, board and cancellation terms, with taxes included and currencies converted. In our constructed example, a shortcut check raised five alerts, three of them false; the normalised check raised exactly the two real undercuts.
- Treat an undercut as the start of an investigation: find which partner’s rate leaked, then fix the contract or the distribution setup.
What rate parity means now
Historically, online travel agencies required hotels to offer them prices at least as good as anywhere else. A “wide” parity clause covered every channel; a “narrow” one covered only the hotel’s own website. Both have been dismantled in much of Europe:
- Germany. The Federal Cartel Office prohibited Booking.com’s narrow best-price clauses in 2015, and the Federal Court of Justice confirmed in May 2021 that they breach competition law.
- European Union. In September 2024, the Court of Justice ruled that platform price parity clauses, wide or narrow, cannot in principle be treated as ancillary restraints exempt from competition law.
- Digital Markets Act. Booking.com was designated a gatekeeper in May 2024 and had to comply by November 2024. Parity clauses are prohibited for it under the Act, so hotels and other providers can offer different, including better, prices and conditions on their own websites and other channels, and Booking.com may not use measures with the same effect, such as raising commissions or delisting them.
- Elsewhere. Several countries, including France, Italy, Austria, Belgium and Switzerland, have restricted parity clauses for accommodation platforms in national law.
This is general information, not legal advice; the rules differ by country and contract, so consult counsel on your own agreements.
The practical consequence is that parity has changed sides. A hotel is now usually free to price below the agencies on its own site, and many want to. What it still needs to know is whether anyone is selling its rooms below its own direct price, and how.
Where undercuts come from
| Source | How it happens | What monitoring shows |
|---|---|---|
| Leaked wholesale rates | Net rates meant to be bundled into packages are resold as standalone rooms | Unfamiliar sites below your direct rate, often on metasearch |
| Package unbundling | A tour operator’s hotel-plus-flight rate is broken out and sold as a room | Prices below what any public channel should offer |
| Member and mobile rates | A channel shows a lower price to logged-in or app users | Gaps that appear only on some devices or sessions |
| Currency and market pricing | A channel prices differently by shopper country or currency | Undercuts in some markets and not others |
| Stale or wrong loads | A rate change did not reach every channel | Short-lived gaps on specific dates |
Only some of these are anyone’s fault, and only some can be fixed by a phone call. Monitoring has to show enough detail to tell them apart.
Why naive parity checks raise false alarms
A parity check looks simple: find the same hotel on several sites and compare prices. Done that way, it fills a dashboard with alerts that are not real, and misses some that are. The common reasons:
- Different products. A non-refundable rate or a room-only rate is not an undercut of a flexible rate with breakfast.
- Tax display. Many sites shown to US shoppers display prices before taxes; most European sites include them. The same total can look several percent cheaper.
- Currency. Converting at different rates, or comparing across currencies at all, creates gaps of a percent or two on its own.
- Market. A channel’s price in the United States is not comparable with your direct price as shown in Germany. Compare each market against itself.
- Dates and occupancy. Different stay lengths and guest counts produce different totals for the same nightly rate.
The code
The module below compares quotes only when they sell the same thing on the same terms, normalises tax display and currency, compares each channel with the direct price shown to the same market, and reports the gap. Room codes should be your own normalised codes, mapped from each channel’s wording, because channels describe the same room differently.
from collections import defaultdict
from dataclasses import dataclass
from decimal import ROUND_HALF_UP, Decimal
@dataclass(frozen=True)
class Quote:
channel: str # "direct", or the name of the OTA or metasearch listing
market: str # where the shopper was, e.g. "DE" or "US"
hotel: str
check_in: str
nights: int
room: str # your own normalised room code, not the channel's wording
board: str # "room-only", "breakfast", ...
refundable: bool
price: Decimal # total for the stay, exactly as displayed
currency: str
taxes_included: bool
def product_key(q):
"""Two quotes are only comparable when they sell the same thing on the same terms."""
return (q.hotel, q.check_in, q.nights, q.room, q.board, q.refundable)
def comparable_total(q, fx_to_base, hotel_tax):
"""Total price in the base currency with the destination's taxes included."""
amount = q.price if q.taxes_included else q.price * (1 + hotel_tax[q.hotel])
return (amount * fx_to_base[q.currency]).quantize(Decimal("0.01"), ROUND_HALF_UP)
def find_undercuts(quotes, fx_to_base, hotel_tax, tolerance=Decimal("0.01")):
"""Compare every channel with the direct rate shown to the same market, and report cheaper offers."""
groups = defaultdict(list)
for q in quotes:
groups[(product_key(q), q.market)].append(q)
undercuts, unmatched = [], []
for (key, market), group in groups.items():
direct = [comparable_total(q, fx_to_base, hotel_tax) for q in group if q.channel == "direct"]
if not direct:
unmatched.extend(q for q in group)
continue
best_direct = min(direct)
for q in group:
if q.channel == "direct":
continue
total = comparable_total(q, fx_to_base, hotel_tax)
gap = (best_direct - total) / best_direct
if gap > tolerance:
undercuts.append({"channel": q.channel, "market": market, "hotel": key[0], "check_in": key[1],
"direct": best_direct, "channel_total": total, "gap": round(gap, 3)})
return sorted(undercuts, key=lambda u: u["gap"], reverse=True), unmatched
Two design choices matter. Taxes are applied per hotel, because they belong to the destination, not to the shopper’s country. And quotes with no comparable direct offer are returned separately rather than dropped, since a channel selling a product you do not sell directly is a finding of its own.
A worked example
We tested the module on a constructed set of ten quotes for one hotel and one stay, shown to shoppers in Germany, the United States and the United Kingdom, with two genuine undercuts planted among them. It is an illustration with made-up prices, not a measurement of any real hotel or channel.
| Check | Alerts raised | Real undercuts found | False alarms |
|---|---|---|---|
| Shortcut: displayed price against the direct price in the same market | 5 | 2 | 3 |
| Normalised: same product, taxes included, one currency | 2 | 2 | 0 |
The three false alarms are the classic ones: a US listing that displayed its price before tax and was in fact at parity, a non-refundable rate, and a room-only rate. The normalised check set the last two aside as not comparable and listed them separately. A UK listing 0.5% below direct after conversion stayed under the 1% tolerance, which is about the size of gap that currency conversion alone can create.
Collecting the quotes
The quality of parity monitoring depends on collecting what guests actually see:
- Shop from the guest’s market. Channels and metasearch results differ by visitor country, so collect from inside each market that matters to you. Our test of whether airlines and hotels quote differently by exit country shows why this is not optional.
- Keep sessions consistent. Match language, currency and time zone to the market, as covered in matching proxy geo, timezone and locale, so a channel does not switch you to another market’s prices mid-session.
- Check devices separately. Mobile and app rates can differ from desktop rates; test both where your guests use both.
- Capture the full terms. Record board, cancellation policy, occupancy and whether taxes are included, not just the headline price. Displayed numbers also need parsing by locale, as explained in normalising prices, numbers and dates across locales.
- Sample dates, not just tomorrow. Leaks often show up on specific future dates, so cover a spread of arrival dates and stay lengths.
- Collect politely. Check a sensible set of dates on a schedule rather than hammering search results, and respect each site’s terms.
The same collection approach underpins scraping flight and hotel prices more generally, and our travel use case covers how teams run it across many properties.
From alert to fix
An undercut alert is the beginning of the work, not the end:
- Confirm it. Re-check the offer from the same market, and make sure the booking path really ends at that price.
- Trace it. A test booking, or the rate code and supplier shown at checkout, usually identifies which partner’s rate leaked.
- Act on the source. Tighten the wholesale contract, change which rates a partner receives, or close a channel for affected dates.
- Track it over time. Record undercuts per partner and market. A partner that leaks repeatedly is a commercial conversation, not a monitoring problem.
The bottom line
Rate parity has stopped being a clause the agencies enforce and become a question hotels ask for themselves: is anyone selling my rooms for less than I do? Answering it reliably means collecting prices from the markets where guests book and comparing only identical products, with taxes and currency normalised.
Do that, and parity alerts become rare and real. Skip it, and the dashboard fills with tax displays and non-refundable rates while the leaked rate that is costing you bookings sits among the noise.
Sources and references
- Court of Justice of the European Union, press release on Case C-264/23, Booking.com, 19 September 2024.
- Bundeskartellamt, Federal Court of Justice confirms illegality of Booking.com’s narrow price parity clauses, 2021.
- European Commission, Booking must now comply with the Digital Markets Act, November 2024.
- Constructed test data and code by Shifter, 3 October 2026.