Skip to main content

Astro package

The @booking/astro-embed package embeds the booking flow in Astro sites, including sites on Cloudflare Workers. It uses the same widget bundle as the JavaScript widget and the WordPress plugin, so the booking flow behaves the same.

The package gives you:

  • an Astro integration that adds the widget to every page
  • .astro components that output the widget placeholders
  • a Paytrail return page
  • an optional same-origin API proxy, so you don't need a CORS allow-list entry
  • helpers for your Content-Security-Policy

Set up the integration​

Add the integration to astro.config.mjs:

import { defineConfig } from 'astro/config';
import sessiqEmbed from '@booking/astro-embed';

export default defineConfig({
integrations: [
sessiqEmbed({
config: {
apiUrl: 'https://api.example.com',
tenantId: 'your-tenant-id',
siteId: 'your-site-id',
},
}),
],
});

The integration:

  • adds one inline boot script to every page. It creates window.dataLayer if it is missing, sets window.sessiqConfig, picks the English or Finnish interface text from the page's <html lang>, loads the widget and calls Sessiq.init.
  • serves the widget files from your own site under /sessiq/, with a version number so browsers pick up new builds.
  • calls Sessiq.rescan() after every View Transitions navigation (astro:page-load), so embeds work with <ClientRouter /> and never render twice.
  • logs a clear console error if the widget has not loaded a few seconds after page load, for example because a CSP blocked it.
tip

Set lang on every page (<html lang="fi">), not once in a shared layout. The interface text, dates, times and prices all follow it. To force one language everywhere, set config.locale.

Options​

OptionDefaultDescription
config—Required. The widget configuration. Same fields as Sessiq.init, see below.
defaultLocaleenInterface language when a page has no <html lang>: en or fi.
proxyfalseThe same-origin API proxy. true is short for { enabled: true }. As an object: enabled (default false) and basePath (default /api/sessiq, must start with /).
paytrailReturnRoute/sessiq/booking-returnPath of the Paytrail return page. Must start with /. false turns the page off.
diagnosticTimeoutMs4000Milliseconds after page load before the "widget did not load" console error.

Unknown options are rejected at build time.

config fieldValidation
apiUrlRequired, a URL
tenantIdRequired, not empty
siteId, apiKey, localeOptional, not empty
bookingBaseUrl, termsUrl, privacyUrlOptional, a URL
primaryColor, buttonColor, backgroundColor, surfaceColor, surfaceAltColor, textColor, borderColorOptional, any CSS colour string
debugOptional, boolean

See JavaScript widget → Configuration for what each field does.

Components​

Import a component and place it on a page. Components output plain HTML placeholders. Don't add a client:* directive; the widget renders into the placeholder itself. Every component also accepts class, which is added to the placeholder element.

---
import SessiqBookingWidget from '@booking/astro-embed/components/SessiqBookingWidget.astro';
import SessiqProductGrid from '@booking/astro-embed/components/SessiqProductGrid.astro';
---

<SessiqBookingWidget productId="your-product-id" defaultView="week" />
<SessiqProductGrid siteId="your-site-id" columns={4} orderBy="name" />
ComponentProps (default)Placeholder
SessiqBookingWidgetproductId (required), showSteps (true), compact (false), successUrl, cancelUrl, defaultView ('month')sessiq-booking-widget-container
SessiqCalendarproductId (required), months (2), showPrices (true), minPartySize (1), maxPartySize (20), defaultView ('month')sessiq-calendar-widget
SessiqProductCardproductId (required), showName (true), showPrice (true), showDescription (true), buttonText, useModal (false), theme, bare (false)sessiq-product-card
SessiqProductGridsiteId, limit (12), columns (3), orderBy, showName (true), showPrice (true), showDescription (true), buttonText, useModal (false), theme, bare (false)sessiq-products-grid
SessiqProductListingsiteId, limit (12), columns (3), showPrice (true), showDescription (true)server-rendered cards, see below
SessiqCoursessiteId, columns (3), orderBy, showSteps (true), successUrl, cancelUrl, theme, bare (false)sessiq-courses-grid
SessiqCourseProductproductId (required), siteId, showSteps (true), successUrl, cancelUrl, theme, bare (false)sessiq-course-product-container
SessiqCoursecourseId (required), showSteps (true), compact (false), successUrl, cancelUrlsessiq-course-widget-container
SessiqWaiverbookingId, productId, signatureMode ('both'). At least one ID is required, or the build fails.sessiq-waiver-standalone
SessiqCampaignslug (omit for the single active campaign), returnUrl (this page)sessiq-campaign

There is no component for the location-first booking flow. To use it on an Astro page, write its placeholder directly, as described in JavaScript widget → Location-first booking flow:

<div class="sessiq-party-selector-container" data-product-tag="birthday"></div>

Server-rendered product listing​

SessiqProductListing fetches products on the server and outputs plain, crawlable HTML, which helps search engines and avoids layout shift. The widget then turns each product into an interactive card. It needs the Cloudflare adapter, the page must opt out of prerendering (export const prerender = false), and it reads the Worker variables listed under Same-origin API proxy. On a static build it falls back to SessiqProductGrid.

The theme prop​

SessiqProductCard, SessiqProductGrid, SessiqCourses and SessiqCourseProduct accept a theme object. Each field becomes an inline CSS variable on that one placeholder, so it overrides the page-wide colours for that embed only. It matches the WordPress style attributes. Values are cleaned the same way: only letters, digits, spaces and # , . % ( ) _ - are kept.

theme fieldCSS variable
buttonColor--sessiq-button
buttonTextColor--sessiq-button-text
buttonHoverColor--sessiq-button-hover
buttonHoverTextColor--sessiq-button-hover-text
buttonShadow--sessiq-button-shadow
buttonHoverShadow--sessiq-button-hover-shadow
buttonRadius--sessiq-button-radius
buttonPadding--sessiq-button-padding
buttonBorder--sessiq-button-border
buttonFontSize--sessiq-button-font-size
buttonWeight--sessiq-button-weight
buttonLetterSpacing--sessiq-button-letter-spacing
buttonUppercase--sessiq-button-transform: uppercase when true
primaryColor--sessiq-primary
textColor--sessiq-text
mutedColor--sessiq-text-muted
backgroundColor--sessiq-background
borderColor--sessiq-border
radius--sessiq-radius

bare removes the default card background, border, shadow and padding.

<SessiqProductGrid
siteId="your-site-id"
bare
theme={{ primaryColor: '#0af', buttonRadius: '999px', buttonUppercase: true }}
/>

See Theming for how these combine with your branding.

Paytrail return page​

When a customer pays with a Finnish bank through Paytrail, the browser leaves your site and comes back. The integration adds a page at /sessiq/booking-return (change it with paytrailReturnRoute). The page loads the widget, which shows the booking result. If the URL has a productId query parameter, the page also shows the booking flow for that product, restored from the customer's session; without it, the page says the booking has been processed.

The booking itself is confirmed by Sessiq on the server, so a customer who closes the tab still has a valid booking.

By default the widget returns the customer to the page they booked on, which also works. Use the return page if you want a dedicated landing page: point successUrl / cancelUrl at it. If your booking pages already handle the return, set paytrailReturnRoute: false.

Same-origin API proxy​

By default the browser calls the Sessiq API directly, so your site's origin must be on the site's Allowed widget origins list (see Before you start).

With the proxy on, the browser calls your own site instead and a Worker route forwards the request:

sessiqEmbed({
config: { apiUrl: 'https://api.example.com', tenantId: 'your-tenant-id' },
proxy: true, // or { enabled: true, basePath: '/api/sessiq' }
});
  • The integration adds a route at <basePath>/[...path] and points the widget's apiUrl at it.
  • The tenant ID and API key are not put in the page. The Worker adds them to each request from its environment.
  • Only API paths under /api/v1/ are forwarded, and only the GET, HEAD, POST, PUT, PATCH, DELETE and OPTIONS methods. Other requests are refused.
  • Only the Content-Type, Accept and Accept-Language request headers are passed on. Responses are never cached.
  • No CORS and no allow-list entry are needed.

The proxy needs an SSR adapter such as @astrojs/cloudflare; the build warns if there is none. Set these Worker environment variables:

VariableRequiredPurpose
SESSIQ_API_URLYesThe Sessiq API origin.
SESSIQ_TENANT_IDYesYour tenant ID, sent to the API with each request.
SESSIQ_API_KEYNoA public API key, sent with each request.

Content-Security-Policy​

The integration does not change your site's CSP. The booking flow loads Stripe Elements and redirects to Paytrail, so your policy must allow them. Import the helpers from @booking/astro-embed/csp and merge the Sessiq part into your own policy:

import {
buildSessiqCsp,
mergeCsp,
serializeCsp,
} from '@booking/astro-embed/csp';

const sessiqCsp = buildSessiqCsp({ apiOrigin: 'https://api.example.com' });
// With the proxy on, the API is same-origin:
// buildSessiqCsp({ apiOrigin: "'self'" });

const policy = serializeCsp(mergeCsp(mySiteCsp, sessiqCsp));
// Send it as the Content-Security-Policy header.
ExportDescription
buildSessiqCsp(opts)Returns the directives the booking flow needs. Options: apiOrigin (required), imageOrigins (extra image hosts), allowInlineStyles (default true), allowInlineScripts (default true).
mergeCsp(base, add)Merges two directive maps and removes duplicate sources.
serializeCsp(map)Turns a directive map into a header value.
STRIPE_SCRIPT_ORIGIN, STRIPE_API_ORIGIN, STRIPE_FRAME_ORIGINS, PAYTRAIL_ORIGINSThe fixed Stripe and Paytrail origins.

buildSessiqCsp returns these directives:

DirectiveSources
default-src'self'
script-src'self', https://js.stripe.com, 'unsafe-inline' (unless allowInlineScripts: false)
connect-src'self', the API origin, https://api.stripe.com, https://services.paytrail.com, https://pay.paytrail.com
frame-srchttps://js.stripe.com, https://hooks.stripe.com, https://services.paytrail.com, https://pay.paytrail.com
img-src'self', data:, the API origin, any imageOrigins
style-src'self', 'unsafe-inline' (unless allowInlineStyles: false)

If you turn off allowInlineScripts or allowInlineStyles, give the inline boot script and styles a nonce through Astro's CSP support. If you use a tag manager, add its origins to your policy yourself.

Without the package​

You can also embed the widget in Astro by hand, as on any other site (see JavaScript widget). Every Sessiq <script> tag must then carry is:inline. Astro otherwise bundles and reorders scripts, and window.Sessiq ends up undefined.

<script is:inline src="/sessiq/sessiq.min.js"></script>
<script is:inline>
window.Sessiq.init({ apiUrl: 'https://api.example.com', tenantId: 'your-tenant-id' });
</script>