Booking page for developers
Three things you can do from your own website or software. Each has its own requirement, so you only need what you use.
| I want to | It needs |
|---|---|
| Show the booking page on my own website | The Website plugin add-on, and your website listed under Allowed websites (up to 2). |
| Hear about each booking as it happens | A plan with webhooks (Pro and up), or the API access add-on (which includes 2 webhooks). Booking events are one of the things a webhook can send. |
| Read or manage bookings from my software | An API key: included with the Business and Agency plans, or the API access add-on on other plans. |
See the add-ons on the Pricing page. Customers always book on our page; we never ask your visitors for anything beyond the booking form.
1. Put the booking page on your website
Open your booking page in the studio, go to Website, add your site under Allowed websites, then paste one line where you want the button or the page:
<script src="https://freeqrstudio.in/embed.js"
data-page="your-page"
data-mode="button"
data-text="Book now"
async></script>It works on any website that lets you add a script, such as WordPress, Wix, Shopify or plain HTML. The studio also has ready-made pieces to copy.
| Attribute | Default | What it does |
|---|---|---|
| data-page | required | The short name in your booking page's address (the part after /book/). |
| data-mode | button | button opens the page over your site. inline puts the page where the tag is. floating puts a round button in the corner. none adds no button, so you open it from your own (see below). |
| data-text | Book now | The words on the button. |
| data-color | #6d4aff | The button colour, as #rrggbb. |
| data-shape | rounded | rounded, pill or square. |
| data-size | m | s, m or l. |
| data-style | solid | solid, outline or 3d. |
| data-icon | none | Up to 4 characters shown before the words, such as an emoji. |
If your website is not in the Allowed websites list, the booking page will not open inside it: your visitors see an empty box. Add the exact address, such as https://www.yourshop.com.
Know when someone books
When a visitor finishes a booking, the page tells your site with a browser event. Use it to count conversions in your own analytics:
window.addEventListener("qrbooking:booked", (e) => {
// e.detail.ref is the booking reference, e.detail.page is your page's short name
gtag("event", "booking", { ref: e.detail.ref });
});Open it from your own button
<script src="https://freeqrstudio.in/embed.js" data-page="your-page" data-mode="none" async></script> <button onclick="QRBooking.open()">Reserve a table</button>
With data-mode="none" the script adds no button of its own, only QRBooking.open() and QRBooking.close(), so any button, link or menu item on your site can open the booking page over it.
Language
If you switched on Hindi or Malayalam (part of Booking extras), a visitor can pick the language on the page, and the page starts in their browser's language. To link straight to one, add ?lang=hi or ?lang=ml to the page address.
2. Hear about each booking: webhooks
In Dashboard, Integrations, add a webhook address and tick the booking events. We send a signed message to your address each time:
booking.created: someone booked. A weekly run sends one message for each visit.booking.moved: a customer moved their booking.booking.cancelled: a booking was cancelled, by the customer or by you.
These messages contain the customer's name, phone and email, so they are off until you tick them.
POST your-address
Content-Type: application/json
X-QRStudio-Event: booking.created
X-QRStudio-Delivery: dl_…
X-QRStudio-Signature: t=1762080000,v1=<hex>
{
"id": "evt_…",
"type": "booking.created",
"created": "2026-11-02T09:30:00.000Z",
"data": {
"booking": { "ref": "BK-7K3M9", "page_id": "…", "service": "Haircut", "person": "Asha",
"start": 1762418400, "end": 1762421100, "when": "Thu 6 Nov, 11:30 am",
"price": 600, "deposit": 100 },
"customer": { "name": "Priya Nair", "phone": "+919876543210", "email": "[email protected]" }
}
}Times are in seconds since 1970 (UTC). booking.moved adds previous_start, and booking.cancelled adds cancelled_by.
Check a message is really from us
The signature is an HMAC-SHA256 of <t>.<exact body> with your webhook's secret. Reject anything older than 5 minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody is the exact text of the request (do not parse it first).
function isFromUs(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=")));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return false; // too old
const expected = createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(parts.v1 || "");
return a.length === b.length && timingSafeEqual(a, b);
}Answer with any success code (200 to 299) quickly, and do the real work afterwards. If your address does not, we try again after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours (six tries in all), then give up on that message. Every try carries the same id, so ignore one you have already handled. After 25 failures in a row the webhook is paused and you are emailed.
3. Read and manage bookings from your software
Make a key in Dashboard, Integrations and send it with every call, from your server, never from a web page. See the codes API for how keys work; the same key reaches these addresses too.
curl "https://freeqrstudio.in/api/bookings?when=upcoming" \ -H "Authorization: Bearer qrs_your_key_here"
| Call | Address | What it does |
|---|---|---|
| GET | /api/bookings?when=upcoming | Your bookings, soonest first. Use when=past for the earlier ones, latest first. Up to 300 are sent. |
| POST | /api/bookings/mark | Mark a booking done or not done. Send { "source": "page", "responseId": 123, "done": true }. |
| POST | /api/bookings/cancel | Cancel a booking and free its time. Send { "source": "page", "responseId": 123 }. Add "notify": true and "message": "…" to email the customer. |
| GET | /api/booking-pages | Your booking pages: id, address, whether it is live or a draft, and its name. |
| GET | /api/booking-pages/{id} | One page with its full setup: services, people, hours, rules and design. |
| PATCH | /api/booking-pages/{id} | Change a page (config, design, status live or draft, address). The same checks as the studio apply, including plan and add-on limits. |
| DELETE | /api/booking-pages/{id} | Delete a page. Its address stops working. Past bookings stay in your list. |
Each booking in the list has responseId (use it to mark or cancel), slot (the date and time in India time), the customer's name, phone and email, and details: the service, who it is with, and the customer's note. The note also holds their answers to your own questions, the number of people, any add-ons and the offer code they used. Bookings made on your forms with a time slot are in the same list, marked with source.
Limits and errors
- Calls a minute: 60 on Business Lite, 120 on Business, 180 on Business Plus, and 300 or more on Agency. The API access add-on gives 30 and 1 key. Over the limit you get
429and aretry-afterheader. - A read-only key can only read. Make a read and write key to mark, cancel or change.
- 401 the key is wrong or was revoked. 403 the key is read-only for that call, the address is not one a key may use, or your plan no longer includes the API.
- Errors come back as
{ "error": "…" }with a plain sentence.
What is not offered
There is no call to make a booking from your own form. If you want your own look, use the Your code tab in the studio (your own HTML and CSS around our booking form), or the embed above. This keeps double bookings and spam out, because every booking goes through the same time checks.
Something missing? Tell us at [email protected]. The codes API is documented separately.