Add ticket widgets to any website
Copy one snippet from the dashboard, paste it into an organizer site, and send buyers to your hosted event pages.
5-second quick start
Paste this anywhere inside <body>. It works in any browser, any site builder.
Listings widget
<div
class="tickets-widget"
data-widget-type="listings"
data-organizer="demo"
data-limit="3"
data-primary-color="#F2CC5D"
></div>
<script async src="https://gigshack-tickets-staging.up.railway.app/v1/loader.js"></script>Heads up: The snippet uses synthetic demo values (organizer demo). For your real site, copy the snippet from the dashboard instead — it has your real organizer slug and brand color.
Build your own snippet
Pick a widget type, fill in the fields, and copy the live-updating snippet. The preview on the right is a real widget rendering with your settings.
Live preview
Snippet
<div
class="tickets-widget"
data-widget-type="listings"
data-organizer="demo"
data-limit="6"
data-primary-color="#F2CC5D"
data-font-family="Inter, system-ui, sans-serif"
></div>
<script async src="https://gigshack-tickets-staging.up.railway.app/v1/loader.js"></script>Live demos
These demos use rolling synthetic events, so dates stay in the future without creating database rows.
Buy button
A compact call-to-action for a specific event. The button opens the hosted event page in a new tab.
Snippet
<div
class="tickets-widget"
data-widget-type="buy"
data-event-id="evt_DEMO"
data-label="Buy tickets"
data-primary-color="#F2CC5D"
></div>
<script async src="https://gigshack-tickets-staging.up.railway.app/v1/loader.js"></script>Event listings
A responsive list of upcoming event cards for one organizer. Each card opens the hosted event page in a new tab.
Snippet
<div
class="tickets-widget"
data-widget-type="listings"
data-organizer="demo"
data-limit="3"
data-primary-color="#F2CC5D"
></div>
<script async src="https://gigshack-tickets-staging.up.railway.app/v1/loader.js"></script>How it works
Copy a snippet from the dashboard
Event pages show a Buy button snippet. The events list page shows a Listings snippet per organizer. Both include your real event IDs, brand color, and loader URL.
Paste into your website
Use an HTML/embed block in WordPress, Squarespace, Webflow, or any plain HTML page. See the platform guide below for site-builder-specific steps.
Buyer clicks → hosted checkout
The widget links to your hosted event page in a new tab. Basket, payment, and ticket delivery continue through the normal flow — nothing changes for you.
Configuration attributes
Every attribute is read from the <div> and validated by the loader. Invalid colors or fonts fall back to safe defaults.
All attributes
| Field | Type | Description |
|---|---|---|
data-widget-typerequired | enum | `buy` or `listings`. Required. |
data-event-idrequired | string | Public event ID generated by the dashboard (format: `evt_XXXX`). Required for Buy. |
data-organizerrequired | string | Organizer slug from the dashboard. Required for Listings. |
data-label | string | Button label. Defaults to `Buy tickets`. |
data-limit | integer 1-24 | Number of event cards to show. Defaults to 6. |
data-category | string | Optional event category filter (e.g. `music`, `comedy`). |
data-primary-color | hex color | Button/card accent color. Must match `^#[0-9a-fA-F]{6}$`. Defaults to `#F2CC5D`. |
data-font-family | font stack | CSS font family. Must match `^[a-zA-Z0-9 ,'-]{1,100}$`. Defaults to `Inter, system-ui, sans-serif`. |
Validation rules
- Colors must match
^#[0-9a-fA-F]6$(six-digit hex with#). Invalid colors fall back to#F2CC5D. - Font families must match
^[a-zA-Z0-9 ,'-]{1,100}$(no quotes, no semicolons). Invalid values fall back toInter, system-ui, sans-serif. - Limit is clamped to
1-24even if a higher value is sent. - Event IDs and organizer slugs are URL-encoded before being passed to the API.
Platform guides
Pick your site builder for step-by-step paste instructions.
Pick your platform
Click a platform for step-by-step paste instructions.
WordPress
- In the post or page editor, add a new Custom HTML block where you want the widget to appear.
- Paste the full snippet (the `<div>` and the `<script>`) into the block.
- Click Preview to verify the widget renders correctly.
- Click Update / Publish to make the page live.
Heads up: If your theme or a security plugin strips `<script>` tags from the block content, add the loader once in your theme footer: go to Appearance → Theme File Editor → Theme Footer (`footer.php`), then just before `</body>` paste only the `<script async src="…/v1/loader.js"></script>` line. Keep the `<div class="tickets-widget" …></div>` inside the Custom HTML block.
Squarespace
- Edit the page, click + Add Block, and choose Code.
- Paste the full snippet into the code block.
- Click Apply then Save.
Heads up: If Squarespace rejects the `<script>` tag, use the iframe embed alternative and put the `<iframe>` in a Code block. Iframe URL: `https://your-domain.example/tickets/embed/:slug`.
Webflow
- In the Webflow Designer, drag an Embed element onto the page where the widget should appear.
- Paste the full snippet into the embed code editor.
- Click Save & Close, then publish the site.
Heads up: If Webflow's HTML sanitiser strips the `<script>`, add the loader to Project Settings → Custom Code → Footer Code (just the script tag) and keep the `<div>` inside the Embed element.
Wix
- Click + Add → More → HTML iframe.
- Set the Source URL to `https://your-domain.example/tickets/embed/:slug` for the event you want to embed.
- Set the width to 100% (or a fixed pixel value) and height to 380.
- Click Apply then Publish.
Heads up: Wix's HTML embed element strips `<script>` tags, so the JS widget will not work on Wix. The iframe embed alternative is the recommended approach. Wix sites that need the listings grid should use one iframe per event, or use Wix Velo to fetch the public widget API directly.
iframe embed alternative
If your site builder strips <script> tags (most notably Wix), use the iframe embed alternative. It renders a pre-styled ticket card at a fixed URL.
iframe embed
<iframe
src="https://your-domain.example/tickets/embed/friday-sessions"
width="100%"
height="380"
style="border:0; max-width: 480px;"
loading="lazy"
title="Buy tickets"
></iframe>The iframe returns a standalone HTML page with the event details, available ticket types, and a "Buy tickets" button that links to the hosted event page. It is responsive up to 480px wide.
Advanced: styling and lifecycle
Shadow DOM isolation
The widget renders inside an open Shadow DOM attached to your <div>. Host page CSS cannot reach inside, and widget CSS cannot leak out. The only way to customize appearance is through the `data-*` attributes.
Lifecycle
The loader runs on page load, on DOMContentLoaded, on `load`, and via a MutationObserver. Widgets added dynamically (e.g. via SPA route changes) are picked up automatically. You can also force a re-scan manually with window.TicketsWidgets.initAll().
CSS custom property
The widget sets --tickets-primary inside its Shadow DOM, used for the button background and CTA pill. This is internal — you cannot override it from outside the Shadow DOM.
Caching
The loader script is served with Cache-Control: public, max-age=300, stale-while-revalidate=3600. The first page load downloads the script; subsequent loads use the cached version for 5 minutes and silently revalidate in the background for up to an hour.
Public API response fields
The loader fetches from the public widget API. Each event returned has this shape:
PublicWidgetEvent
| Field | Type | Description |
|---|---|---|
public_idrequired | string | Public event ID in `evt_XXXX` format. |
namerequired | string | Event name. |
slugrequired | string | URL slug of the hosted event page. |
summary | string | null | Short event description. |
start_timerequired | ISO 8601 string | Event start time in UTC. |
end_timerequired | ISO 8601 string | Event end time in UTC. |
image_url | string | null | Absolute URL of the event image, or null. |
categoryrequired | string | Event category (e.g. `music`, `comedy`, `theatre`). |
age_restrictionrequired | string | e.g. `all_ages`, `18_plus`. |
event_formatrequired | string | e.g. `in_person`, `online`. |
venue_name | string | null | Venue name. |
venue_city | string | null | Venue city. |
urlrequired | string | Absolute URL to the hosted event page. Widget links go here. |
min_price | integer | null | Lowest public ticket type price in the smallest currency unit (pence, cents). `null` if no public tickets. |
currency | string | null | ISO 4217 currency code (e.g. `GBP`). |
availability_statusrequired | string | Computed sale status: `on_sale`, `upcoming`, `sold_out`, `closed`. |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Buy button shows "Event is not available." | Event is unpublished, sold out, sale window is in the future, or the public ID is wrong. | Verify the event is **published** in the dashboard, then copy the exact `data-event-id` from the dashboard snippet. The ID is case-sensitive. |
| Listings widget shows "No upcoming events are available." | No upcoming published events for the organizer. | Verify the organizer has at least one published event with a `start_time` in the future. The widget filters out past and unpublished events. |
| Listings widget shows "Events are not available." (generic error) | Wrong organizer slug, or the network request failed. | Verify `data-organizer` matches the organizer slug in the dashboard URL. Check the browser dev tools Network tab for the `/api/public/widgets/events` request. |
| Widget renders nothing at all | The `<script>` tag was stripped by the editor. | Use the iframe embed alternative, or move the script tag to your site footer (e.g. WordPress `footer.php`, Webflow Project Settings → Custom Code → Footer Code). |
| Widget renders but links go to a 404 | `WIDGET_ORIGIN` / `PUBLIC_ORIGIN` is misconfigured. | Verify the env var matches the public-facing domain where the event page is hosted. The widget computes links as `WIDGET_ORIGIN + /tickets/events/:slug`. |
| Wrong colors or fonts in the widget | Invalid hex color or font family value. | Colors must be six-digit hex with `#` (e.g. `#F2CC5D`). Font families must match `^[a-zA-Z0-9 ,'-]{1,100}$` — no quotes, no semicolons. |
| "Too Many Requests" / 429 | Rate limit exceeded (240 requests/min per IP for the public widget API). | Reduce request frequency, cache the loader, or contact support if you need a higher limit. |
| Console error: `evt_DEMO` event not found in production | The demo event only exists in the public widget API on the docs page, not in your dashboard. | Use a real `data-event-id` from the dashboard in production snippets. `evt_DEMO` is only for the docs page at `/developers/widgets`. |
Security and public data
No secrets
Widget snippets do not contain API keys. Public endpoints return only active organizer, published event data.
No credentials
The loader fetches with credentials: 'omit'. The browser does not send cookies. No write operations are exposed via the public API.
Server-side filtering
Listings exclude past events and unpublished events at the database query level, not just the response level. Private ticket types are excluded from pricing.
Link safety
All clickable links use rel="noopener noreferrer" and target="_blank" for security and performance.
Next steps
Use the API
Read event data programmatically with API keys. See the API docs for full reference, response examples, and a working playground.
API docs →Quick start
Two-track 5-minute walkthrough: embed widgets OR use the API. Great for first-time integrators.
Quick start →