Developers
Integration Guide
Add bookings to your site in minutes. Choose iframe embed for zero-code, or the JavaScript SDK for full control.
What you need before starting
- Your Tenant ID and Publishable Key from Settings → API Keys
- Your Widget Config ID from Dashboard → Widgets (optional, for theming)
- Your app URL (e.g.
https://your-app.vercel.app)
API keys for headless use
When calling the API directly (headless / custom UI), create keys in Settings → API Keys.
- Publishable key (
zentier_pk_live_…orzentier_pk_test_…). Safe for frontend code when scoped. Default scopes include widget read, booking read, and booking write for headless flows. - Private key (
zentier_sk_live_…orzentier_sk_test_…). Backend and Postman only. Full API access. Click Private key in Settings → API keys. The key is shown once — copy it to your.envor Postman environment immediately.
Never expose private keys
Testing with Postman or cURL
Import the ready-made collection from postman/Zentier-Headless.postman_collection.json in this repository, then load postman/Zentier-Local.postman_environment.json and set api_key, base_url, and resource_id.
Get your keys
Go to Settings → API keys and create a Private key for Postman (full access) or a Publishable key with booking scopes. You also need a Resource ID from the dashboard.
Call availability
curl -sS "https://YOUR_APP_URL/api/widget/availability?key=YOUR_PUBLISHABLE_KEY&resource_id=YOUR_RESOURCE_ID&start_date=2026-06-01&end_date=2026-06-30"GET https://YOUR_APP_URL/api/widget/availability?resource_id=YOUR_RESOURCE_ID&start_date=2026-06-01&end_date=2026-06-30
Headers:
x-publishable-key: YOUR_PUBLISHABLE_KEYCreate a booking
curl -sS -X POST "https://YOUR_APP_URL/api/widget/booking" \
-H "Content-Type: application/json" \
-H "x-publishable-key: YOUR_PUBLISHABLE_KEY" \
-d '{
"resource_id": "YOUR_RESOURCE_ID",
"slot_id": "SLOT_UUID",
"customer_name": "Jane Doe",
"customer_email": "jane@example.com",
"amount_cents": 0,
"currency": "SEK"
}'POST https://YOUR_APP_URL/api/widget/booking
Headers:
Content-Type: application/json
x-publishable-key: YOUR_PUBLISHABLE_KEY
Body (raw JSON):
{
"resource_id": "YOUR_RESOURCE_ID",
"slot_id": "SLOT_UUID",
"customer_name": "Jane Doe",
"customer_email": "jane@example.com",
"amount_cents": 0,
"currency": "SEK"
}Response codes
- 200 OK. Success
- 401 Unauthorized. Missing or invalid API key
- 403 Forbidden. API key missing required scope
- 409 Conflict. Slot already taken or insufficient capacity
- 429 Too Many Requests. Rate limited
Option 1. Iframe Embed (Easiest)
Drop an iframe into any HTML page. No JavaScript required. The booking UI loads inside the iframe and handles availability, selection, and checkout automatically.
Copy the iframe code
Replace the placeholders with your actual values:
<iframe
src="https://YOUR_APP_URL/embed/YOUR_TENANT_ID?key=YOUR_PUBLISHABLE_KEY&widget=YOUR_WIDGET_CONFIG_ID"
width="100%"
height="720"
style="border:0;border-radius:12px;box-shadow:0 1px 3px rgba(0,0,0,0.1)"
title="Bookings"
allow="payment"
></iframe>Add allowed origins (required for any external domain)
For security, every domain that loads the widget must be pre-registered. Go to Settings → Widget embed apps and add the full origin (scheme + host), e.g. https://www.example.com. The same allowlist is used for two things:
- CORS. so the browser can call
/api/widget/*from that site. - CSP
frame-ancestors. so the browser allows the/embed/*page to be loaded inside an iframe from that site.
If a customer reports "frame-ancestors" ... blocked in DevTools, they need to add their domain here. Self-hosted installs can also set WIDGET_ALLOWED_ORIGINS as a platform-wide override.
Customize the look
Go to Dashboard → Widgets to set colors, button text, and layout. Changes apply instantly. no code redeploy needed.
Height tip
height="720" or more for the full booking flow. You can also use min-height in CSS and let the iframe scroll internally.Option 2. JavaScript SDK (More Control)
Load /sdk/booking-widget.js with a script tag. The SDK can open a modal popup or render inline inside any element on your page.
Add the script tag
<script
src="https://YOUR_APP_URL/sdk/booking-widget.js"
data-tenant="YOUR_TENANT_ID"
data-key="YOUR_PUBLISHABLE_KEY"
data-widget-config="YOUR_WIDGET_CONFIG_ID"
data-api-url="https://YOUR_APP_URL/api"
data-embed-url="https://YOUR_APP_URL"
data-button-text="Book Now"
></script>Choose a mode
The SDK supports two modes:
- Modal (default). renders a button that opens a booking popup.
- Inline. embeds the widget directly into a page element.
<div id="booking-widget"></div>
<script
src="https://YOUR_APP_URL/sdk/booking-widget.js"
data-tenant="YOUR_TENANT_ID"
data-key="YOUR_PUBLISHABLE_KEY"
data-widget-config="YOUR_WIDGET_CONFIG_ID"
data-api-url="https://YOUR_APP_URL/api"
data-embed-url="https://YOUR_APP_URL"
data-mode="inline"
data-target="booking-widget"
></script>Listen for events (optional)
React to bookings, errors, and modal closes from your own code:
// Open the modal programmatically
window.BookingWidget.open();
// Listen for a successful booking
window.BookingWidget.on('bookingSuccess', (booking) => {
console.log('Booking confirmed!', booking);
// Send to analytics, show a thank-you message, etc.
});
// Listen for errors
window.BookingWidget.on('bookingError', (error) => {
console.error('Booking failed:', error);
});
// Clean up when needed
// window.BookingWidget.destroy();Option 3. Headless / Custom UI
Build your own UI and call the widget API directly. You have full control over every pixel.
const params = new URLSearchParams({
key: 'YOUR_PUBLISHABLE_KEY',
resource_id: 'YOUR_RESOURCE_ID',
start_date: '2026-06-01',
end_date: '2026-06-30',
})
const res = await fetch(
`https://YOUR_APP_URL/api/widget/availability?${params}`
)
const { slots } = await res.json()const res = await fetch('https://YOUR_APP_URL/api/widget/booking', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Publishable-Key': 'YOUR_PUBLISHABLE_KEY',
},
body: JSON.stringify({
resource_id: 'YOUR_RESOURCE_ID',
slot_id: 'SLOT_UUID', // optional
customer_name: 'Jane Doe',
customer_email: 'jane@example.com',
customer_phone: '+1234567890', // optional
quantity: 1,
amount_cents: 2500,
currency: 'EUR',
}),
})
const { booking, checkout_url } = await res.json()
if (checkout_url) {
// Redirect to Mollie payment page
window.location.href = checkout_url
}Security note
zentier_server_secret_ key in frontend code. Server-only routes belong on your backend.Troubleshooting
- Widget config fails to load. Check DevTools → Network for
widget/config. A 401 means the key is wrong; 403 means the widget is unpublished; CORS errors mean the origin is not inWIDGET_ALLOWED_ORIGINS. - Script not working on another domain. You must set both
data-api-urlanddata-embed-urlto your Zentier app origin. - Payments not redirecting. Ensure Mollie is connected under Dashboard → Payments.
Next steps
- API Reference. full endpoint list with request/response examples.
- Widget Styling. customize colors, fonts, and layout.
- Webhooks. get notified when bookings are created or paid.