Choosing an integration
The widget has four integration surfaces. Which one applies is decided by what the host already has — a page, a page with JavaScript, or only a URL — rather than by preference.
The four surfaces
Section titled “The four surfaces”| You have | Integration | Context arrives as | Identity | Events |
|---|---|---|---|---|
| A page in a CMS, no JavaScript | Script tag | data-* | — | — |
| A page you write JavaScript on | JavaScript API | setContext() | setGuestToken() | Yes |
| Only a link — e-mail, SMS, QR | Deep link | Query string | ?t= | — |
| Nothing to integrate into | The booking page, linked | — | — | — |
The first two put the widget on the host’s own page. The last two send the guest
to the standalone booking page at book.useservice.app.
What decides it
Section titled “What decides it”Identity needs JavaScript or a link
Section titled “Identity needs JavaScript or a link”A signed token reaches the widget through setGuestToken() or through ?t= on
a deep link. There is deliberately no data-token attribute.
An attribute is part of the page’s HTML: it is served to every reader, cached by the CMS and by whatever sits in front of it, and visible in view-source. A token lives 15 minutes and identifies one guest, so a copy baked into a page is both expired and wrong for nearly everyone who receives it.
If the integration must know who is booking, it needs a page that can run
setGuestToken(), or a link minted per guest at the moment it is sent.
Enforced locks need a token; drawn ones do not
Section titled “Enforced locks need a token; drawn ones do not”Any surface can lock a field so the guest sees it as fixed — an attribute, a query parameter or a JavaScript call all do it.
What needs the signed token is enforcement. The server refuses a booking that contradicts a lock only when the lock arrived inside the token, because that is the only version it can trust. An unsigned lock is drawn and not enforced.
That difference decides the surface only when the value must actually hold — a voucher valid on one date, a prepaid slot. For simply reflecting a choice already made on your page, nothing is needed.
Events need a page
Section titled “Events need a page”reservation:created and reservation:confirmed are delivered to the host page
that created the instance. A deep link has no such page: the guest is on
book.useservice.app, and the host is not in the browser at all.
Bookings made through a deep link are reported the same way telephone bookings are — through the API or a webhook, on your server. That path also works for the embedded cases, and is the one to use when the record matters: a browser event is not delivered if the guest closes the tab.
Presentation is a separate choice
Section titled “Presentation is a separate choice”inline, sticky and popover decide how the widget appears on a host page.
They apply to both embedded surfaces and are independent of everything above: a
CMS script tag can be a popover, and a JavaScript integration can be inline.
| Mode | Appears as | Suits |
|---|---|---|
inline | Part of the page, inside your container. | A dedicated booking section or page. |
sticky | A floating button the widget renders itself. | A site with no obvious place to put it. |
popover | Nothing, until a trigger or open() shows it. | Your own button, your own styling. |
Several restaurants on one page
Section titled “Several restaurants on one page”A group site listing eight restaurants creates one instance per restaurant:
const instances = restaurants.map((r) => ServiceWidget.create({ slug: r.slug, mode: "popover" }));Each instance is addressed on its own. ServiceWidget.open() and
ServiceWidget.close() on the global cannot name one among several — close()
closes all of them, deliberately, so that no instance can be left open with
nothing able to reach it.
With declarative triggers, each trigger carries the restaurant it opens:
<button data-service-widget-open data-slug="chez-marie">Book</button><button data-service-widget-open data-slug="le-comptoir">Book</button>Where to start
Section titled “Where to start”- A restaurant’s own site, no developer. Script tag, from the back office. It is complete on its own — no further integration is required to take bookings.
- A group site or a guest portal where the visitor is signed in. JavaScript API with a token: the funnel opens knowing who is booking, and anything the stay already fixes can be locked.
- A campaign, a booking confirmation, a room-key card. Deep link. Add
?t=only where the link is minted per guest and sent immediately — never on anything printed.