# Developer docs How to connect StayParity to your own systems. Signed webhooks for every new undercut, and the book-direct badge for a hotel's website. StayParity has two things you can build on: webhooks that tell your server when an OTA undercuts a hotel, and a badge that shows guests on the hotel's own website that booking direct pays. ## Webhooks When a check finds an OTA selling a room for less than the hotel's direct price, StayParity posts the undercut to your URL as signed JSON. Use it to open a ticket, message a revenue manager or feed your own reporting. Deliveries follow Standard Webhooks, so an off-the-shelf library verifies them in a few lines. Read [Webhooks](/developers/webhooks) for the event, signatures, retries and testing. ## Website badge One ` ``` 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. | Attribute | Default | What it does | | ---------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data-hotel` | none, required | The hotel's embed key, `wk_…`. Without it the tag renders nothing. | | `data-book-url` | none | The 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-stay` | tomorrow | The 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-occupancy` | `2` | Guests, 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: ```js title="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: | When | Headline | Also shows | | ---------------------------------- | -------------------------- | ---------------------------------------------- | | The hotel is cheapest | **Best price, right here** | The OTA prices, and "Save … by booking direct" | | The hotel matches the cheapest OTA | **Same price here** | The OTA prices | | An OTA is cheaper | **Our price today** | Only the direct price | | No OTA price for that night | **Our price today** | Only 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. It shows **Book direct with us** straight away, without the wait, when: * the hotel is inactive in StayParity. * the occupancy isn't one the hotel is monitored on, and no price has been read for it. * a check is refused: the stay is in the past or further ahead than the hotel's plan checks, or the hotel's allowance of checks is used up. * the 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. Until that answer arrives, it shows **Checking today's prices…** in a box about 96 pixels tall. If that request is rate-limited instead, it shows **Book direct with us**. A guest's browser may keep showing a card it loaded in the last five minutes. 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: ```text 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.