Skip to content
StayParity

Website badge

Add StayParity's book-direct badge to a hotel's website with one script tag. The snippet, its options, what guests see, and the CSP it needs.

The badge is a small card on the hotel's own website. It shows the direct price for a stay and, when the hotel is cheapest or level, the OTA prices beside it. It runs in a sandboxed iframe, so it can't read the page it sits on and the page can't reach into it.

What the badge says to guests, and why, is on The book-direct badge. This page is about putting it on a site.

Add it to a page

  1. In the portal, go to Settings → Website badge and turn Badge on.
  2. Copy the snippet from the Snippet group. It already holds the hotel's embed key. Check data-book-url: when no booking link is configured, the snippet contains https://your-hotel.example/book. Replace that placeholder with your booking URL, or remove the attribute to leave out the button.
  3. Paste it where the badge should appear, usually next to the date picker on the booking page.
The snippet
<!-- StayParity badge. Put it where guests pick dates. -->
<script src="https://badge.stayparity.com/embed.js"
        data-hotel="wk_aurora"
        data-book-url="https://hotelaurora.gr/book"
        async></script>

The script puts an iframe right after its own tag and does nothing else to the page. The frame fills the width of whatever holds it and sets its own height to fit the card. Paste the tag once per place you want a badge.

Options

Everything is a data- attribute on the script tag.

AttributeDefaultWhat it does
data-hotelnone, requiredThe hotel's embed key, wk_…. Without it the tag renders nothing.
data-book-urlnoneThe hotel's booking page. Adds a Book direct button that opens it in the same tab. Only https:// addresses; anything else is ignored and the button is left out.
data-staytomorrowThe night to price, as YYYY-MM-DD. "Tomorrow" is in the guest's own time zone. A date that doesn't exist falls back to tomorrow.
data-occupancy2Guests, a whole number from 1 to 10. Anything else falls back to 2.

The badge starts a fresh check only for the occupancies the hotel is monitored on. For any other, it shows a price only when one has already been read.

Change the stay from your page

When a guest picks dates on your booking form, tell the badge so it prices those instead:

booking-form.js
// When the guest picks dates
const frame = document.querySelector('iframe[src^="https://badge.stayparity.com/"]');
frame.contentWindow.postMessage(
  { type: "rate-parity:stay", stay: "2026-10-16", occupancy: 2 },
  "*"
);

stay is a YYYY-MM-DD string and occupancy a number, with the same rules as the attributes. Convert a form value such as "3" to a number before sending it; a string occupancy is ignored. Leave one out to keep its current value. The frame accepts this message only from the page it sits on, and the hotel can't be changed by message.

Send messages only after the frame has loaded. Earlier messages are dropped; a frame below the fold loads lazily, so it may not have loaded when the guest first picks dates. Set data-stay and data-occupancy for the initial selection.

The target is "*" because the sandboxed frame has no origin of its own to name. That's safe here: the message holds only a date and a number of guests.

What guests see

The badge compares the direct price with the OTA prices for that night and shows these price cards:

WhenHeadlineAlso shows
The hotel is cheapestBest price, right hereThe OTA prices, and "Save … by booking direct"
The hotel matches the cheapest OTASame price hereThe OTA prices
An OTA is cheaperOur price todayOnly the direct price
No OTA price for that nightOur price todayOnly the direct price

Each card shows how long ago the prices were checked, and the Book direct button if the tag has a booking link.

While the first check for a stay runs, the card says Checking today's prices…. If no price arrives within about 30 seconds, it says Book direct with us and keeps the button. That fallback can also appear immediately when prices are unavailable, a check cannot run, or a request fails or is rate-limited.

Prices are in the hotel's own currency, formatted for the guest's locale. The card's words are in English.

What it never shows

  • An OTA price below the hotel's own. When an OTA is cheaper, the badge lists no OTAs at all rather than only the dearer ones. The hotel still gets the undercut in the portal and in its alerts.
  • The hotel's internal id, where a price was read, or how sure we are of it.
  • An error. If the server answers that the key is unknown or the badge is off, the frame shrinks away after its first request. It may briefly show Checking today's prices… first. If that request is rate-limited instead, it shows Book direct with us.

The frame sets no cookies and stores nothing in the guest's browser.

Content Security Policy

If the site sends a Content-Security-Policy header, allow the badge's origin to load the script and the frame:

script-src https://badge.stayparity.com;
frame-src https://badge.stayparity.com;

Add these to the sources the site already allows rather than replacing them. The script sets the frame's size through the DOM, not inline style attributes in markup, so style-src needs no change. The prices are fetched inside the frame, under the frame's own policy, so connect-src needs no change either. If your script policy uses nonces or strict-dynamic, give the script tag the nonce your site uses; adding a host to the allowlist alone may not allow it.

Turn it off or replace it

  • Turn off: switch Badge off in Settings → Website badge. New visitors see nothing, as the frame shrinks away; a guest's browser may keep showing a card it loaded in the last five minutes. Switch it back on and the same snippet works again.
  • Replace: Replace snippet issues a new embed key. The old key is refused on the next uncached request, on every site that has it, until you paste the new snippet. A browser may keep showing a card cached in the last five minutes.

Only account admins can turn the badge on or off, or replace the snippet.

Performance

The script loads with async, so it never holds up the page. The frame loads lazily: a badge far down the page loads when the guest scrolls near it. It asks for prices only for the stay it shows.

On this page