Skip to content

Instantly share code, notes, and snippets.

@jmcmullen
Created December 18, 2025 11:33
Show Gist options
  • Select an option

  • Save jmcmullen/a5a63aa4165c85dba0cda956153bdcb2 to your computer and use it in GitHub Desktop.

Select an option

Save jmcmullen/a5a63aa4165c85dba0cda956153bdcb2 to your computer and use it in GitHub Desktop.
Racquet Clubs ticketing integration - docs & demo

Racquet Clubs Ticketing Integration

Embed event ticketing on your website using the Session Services API and web component.


Ticketing Widget

Add the ticket purchasing widget to any page.

1. Load the Component (once per page)

<script type="module" src="https://unpkg.com/@session-services/web-elements"></script>

Custom elements are auto-registered when the script loads.

2. Embed the Widget

<session-services-ticketing
  tenant-id="tnt_01jqpj2t2kfvmstt6f6tzkbaf2"
  event-id="EVENT_ID_HERE"
  return-url="https://yoursite.com/thank-you"
  theme="light"
></session-services-ticketing>

Props

Prop Required Description
tenant-id Yes tnt_01jqpj2t2kfvmstt6f6tzkbaf2
event-id Yes Event ID or slug
return-url No Redirect URL after checkout
theme No light, dark, or system

Custom Styling

<session-services-ticketing
  tenant-id="tnt_01jqpj2t2kfvmstt6f6tzkbaf2"
  event-id="EVENT_ID_HERE"
  style="
    --primary: #ec4899;
    --primary-foreground: #ffffff;
    --card: #ffffff;
    --card-foreground: #111827;
    --border: #e5e7eb;
  "
></session-services-ticketing>

REST API

Fetch events and venues using the API.

Headers (required for all requests)

const headers = {
  'x-tenant-id': 'tnt_01jqpj2t2kfvmstt6f6tzkbaf2',
  'x-api-version': '2025-09-22'
};

Get All Events

const params = new URLSearchParams({
  promoterId: 'pmt_01kcqj2g52e0796vvj59emcyc1'
});

const res = await fetch(`https://api.session.services/events?${params}`, { headers });
const { events } = await res.json();

Get Events by Venue

const params = new URLSearchParams({
  promoterId: 'pmt_01kcqj2g52e0796vvj59emcyc1',
  venueId: 'VENUE_ID_HERE'
});

const res = await fetch(`https://api.session.services/events?${params}`, { headers });
const { events } = await res.json();

Get Single Event

// By slug
const res = await fetch(
  'https://api.session.services/events/summer-festival-2025',
  { headers }
);
const { event } = await res.json();

// By ID
const res = await fetch(
  'https://api.session.services/events/evt_01jps5cgsenjrazw6wswmyspa3',
  { headers }
);
const { event } = await res.json();

Response includes: name, description, dates, location, images, ticket sections.


Get All Venues

const params = new URLSearchParams({
  teamId: 'tem_01kcqj2g52e0796vttfjpy7x9v'
});

const res = await fetch(`https://api.session.services/venues?${params}`, { headers });
const { venues } = await res.json();

Example: Event Page

Fetch event details and display the ticketing widget:

<script type="module" src="https://unpkg.com/@session-services/web-elements"></script>

<div id="event-content">
  <h1 id="event-title"></h1>
  <p id="event-date"></p>
  <p id="event-location"></p>
  <p id="event-description"></p>

  <session-services-ticketing
    tenant-id="tnt_01jqpj2t2kfvmstt6f6tzkbaf2"
    return-url="https://yoursite.com/thank-you"
    theme="light"
  ></session-services-ticketing>
</div>

<script type="module">
  const headers = {
    'x-tenant-id': 'tnt_01jqpj2t2kfvmstt6f6tzkbaf2',
    'x-api-version': '2025-09-22'
  };

  // Get event slug from URL (e.g., /events/summer-festival)
  const slug = window.location.pathname.split('/').pop();

  const res = await fetch(`https://api.session.services/events/${slug}`, { headers });
  const { event } = await res.json();

  document.getElementById('event-title').textContent = event.name;
  document.getElementById('event-description').textContent = event.description;
  document.getElementById('event-location').textContent = event.location?.name;
  document.getElementById('event-date').textContent = new Date(event.entryStartsAt).toLocaleDateString('en-AU', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: 'numeric',
    minute: '2-digit'
  });

  // Set the event ID on the widget
  document.querySelector('session-services-ticketing').setAttribute('event-id', event.id);
</script>

Example: Events List

Display all events:

<div id="events-list"></div>

<script type="module">
  const headers = {
    'x-tenant-id': 'tnt_01jqpj2t2kfvmstt6f6tzkbaf2',
    'x-api-version': '2025-09-22'
  };

  const params = new URLSearchParams({
    promoterId: 'pmt_01kcqj2g52e0796vvj59emcyc1'
  });

  const res = await fetch(`https://api.session.services/events?${params}`, { headers });
  const { events } = await res.json();

  const container = document.getElementById('events-list');

  events.forEach(event => {
    container.innerHTML += `
      <a href="/events/${event.slug}">
        <img src="${event.images?.desktop?.src || ''}" alt="${event.name}" />
        <h3>${event.name}</h3>
        <p>${new Date(event.entryStartsAt).toLocaleDateString('en-AU')}</p>
        <p>${event.location?.name || ''}</p>
      </a>
    `;
  });
</script>

Example: Events by Venue

Display events for a specific venue:

<h1 id="venue-name"></h1>
<div id="events-list"></div>

<script type="module">
  const headers = {
    'x-tenant-id': 'tnt_01jqpj2t2kfvmstt6f6tzkbaf2',
    'x-api-version': '2025-09-22'
  };

  // Get venue slug from URL (e.g., /venues/melbourne-tennis-centre)
  const slug = window.location.pathname.split('/').pop();

  // Get venues and find by slug
  const venueParams = new URLSearchParams({
    teamId: 'tem_01kcqj2g52e0796vttfjpy7x9v'
  });

  const venuesRes = await fetch(`https://api.session.services/venues?${venueParams}`, { headers });
  const { venues } = await venuesRes.json();
  const venue = venues.find(v => v.slug === slug);

  if (!venue) {
    document.body.innerHTML = '<h1>Venue not found</h1>';
    throw new Error('Venue not found');
  }

  document.getElementById('venue-name').textContent = venue.name;

  // Get events for this venue
  const eventParams = new URLSearchParams({
    promoterId: 'pmt_01kcqj2g52e0796vvj59emcyc1',
    venueId: venue.id
  });

  const eventsRes = await fetch(`https://api.session.services/events?${eventParams}`, { headers });
  const { events } = await eventsRes.json();

  const container = document.getElementById('events-list');

  events.forEach(event => {
    container.innerHTML += `
      <a href="/events/${event.slug}">
        <h3>${event.name}</h3>
        <p>${new Date(event.entryStartsAt).toLocaleDateString('en-AU')}</p>
      </a>
    `;
  });
</script>

Widget Events

Listen for cart and checkout events:

const widget = document.querySelector('session-services-ticketing');

widget.addEventListener('cartUpdate', (e) => {
  console.log('Cart updated:', e.detail);
});

widget.addEventListener('checkoutInit', (e) => {
  console.log('Checkout started:', e.detail.orderId);
});

widget.addEventListener('errorOccurred', (e) => {
  console.error('Error:', e.detail.error);
});

Reference

Key Value
API URL https://api.session.services
Tenant ID tnt_01jqpj2t2kfvmstt6f6tzkbaf2
Team ID tem_01kcqj2g52e0796vttfjpy7x9v
Promoter ID pmt_01kcqj2g52e0796vvj59emcyc1
API Version 2025-09-22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Racquet Clubs Events</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #fafafa; color: #111; line-height: 1.6; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
header { background: #fff; border-bottom: 1px solid #e5e7eb; padding: 16px 0; margin-bottom: 32px; }
header .container { display: flex; justify-content: space-between; align-items: center; }
.logo { font-size: 1.5rem; font-weight: 700; color: #ec4899; text-decoration: none; }
select { padding: 8px 12px; border: 1px solid #e5e7eb; border-radius: 6px; font-size: 0.875rem; cursor: pointer; }
select:focus { outline: none; border-color: #ec4899; }
h1 { font-size: 2rem; margin-bottom: 24px; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 24px; }
.card { background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; text-decoration: none; color: inherit; display: block; transition: transform 0.2s; }
.card:hover { transform: translateY(-4px); }
.card img { width: 100%; height: 200px; object-fit: cover; }
.card-body { padding: 16px; }
.card h3 { font-size: 1.25rem; margin-bottom: 8px; }
.card .date { color: #ec4899; font-weight: 600; margin-bottom: 4px; }
.card .location { color: #6b7280; font-size: 0.875rem; }
.event-detail { display: grid; grid-template-columns: 1fr 400px; gap: 32px; }
@media (max-width: 900px) { .event-detail { grid-template-columns: 1fr; } }
.event-info img { width: 100%; border-radius: 12px; margin-bottom: 24px; }
.meta { display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px; color: #6b7280; }
.meta span:first-child { color: #ec4899; margin-right: 8px; }
.ticket-section { position: sticky; top: 20px; }
.ticket-section h2 { font-size: 1.5rem; margin-bottom: 16px; }
.back-btn { display: inline-block; color: #6b7280; text-decoration: none; margin-bottom: 24px; }
.back-btn:hover { color: #ec4899; }
.hidden { display: none !important; }
.loading, .empty { text-align: center; padding: 48px; color: #6b7280; }
</style>
</head>
<body>
<header>
<div class="container">
<a href="#" class="logo">Racquet Clubs</a>
<select id="venue-filter">
<option value="">All Venues</option>
</select>
</div>
</header>
<main class="container">
<section id="view-events" class="view">
<h1 id="page-title">Upcoming Events</h1>
<div id="events-list" class="grid"><div class="loading">Loading events...</div></div>
</section>
<section id="view-detail" class="view hidden">
<a href="#" class="back-btn" onclick="history.back()">← Back</a>
<div class="event-detail">
<div class="event-info">
<img id="detail-img" src="" alt="">
<h1 id="detail-title"></h1>
<div class="meta">
<div><span>πŸ“…</span><span id="detail-date"></span></div>
<div><span>πŸ“</span><span id="detail-location"></span></div>
</div>
<p id="detail-desc"></p>
</div>
<div class="ticket-section">
<h2>Get Tickets</h2>
<session-services-ticketing
id="widget"
tenant-id="tnt_01jqpj2t2kfvmstt6f6tzkbaf2"
theme="light"
style="--primary: #ec4899; --primary-foreground: #fff; --card: #fff; --card-foreground: #111; --border: #e5e7eb;"
></session-services-ticketing>
</div>
</div>
</section>
</main>
<script type="module" src="https://unpkg.com/@session-services/web-elements"></script>
<script>
const API = 'https://api.session.services';
const headers = { 'x-tenant-id': 'tnt_01jqpj2t2kfvmstt6f6tzkbaf2', 'x-api-version': '2025-09-22' };
const PROMOTER = 'pmt_01kcqj2g52e0796vvj59emcyc1';
const TEAM = 'tem_01kcqj2g52e0796vttfjpy7x9v';
const $ = id => document.getElementById(id);
const formatDate = d => new Date(d).toLocaleDateString('en-AU', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: '2-digit' });
const shortDate = d => new Date(d).toLocaleDateString('en-AU', { weekday: 'short', month: 'short', day: 'numeric' });
let venues = [];
function eventCard(e) {
return `<a class="card" href="#event/${e.slug}">
${e.images?.desktop?.src ? `<img src="${e.images.desktop.src}" alt="${e.name}">` : ''}
<div class="card-body"><h3>${e.name}</h3><p class="date">${shortDate(e.entryStartsAt)}</p><p class="location">${e.location?.name || ''}</p></div>
</a>`;
}
async function loadVenues() {
const res = await fetch(`${API}/venues?teamId=${TEAM}`, { headers });
const data = await res.json();
venues = data.venues || [];
$('venue-filter').innerHTML = '<option value="">All Venues</option>' + venues.map(v => `<option value="${v.id}">${v.name}</option>`).join('');
}
async function loadEvents(venueId) {
const params = new URLSearchParams({ promoterId: PROMOTER });
if (venueId) params.set('venueId', venueId);
const res = await fetch(`${API}/events?${params}`, { headers });
const { events } = await res.json();
const venue = venues.find(v => v.id === venueId);
$('page-title').textContent = venue ? `Events at ${venue.name}` : 'Upcoming Events';
$('events-list').innerHTML = events.length ? events.map(eventCard).join('') : '<div class="empty">No upcoming events</div>';
}
async function loadDetail(slug) {
const res = await fetch(`${API}/events/${slug}`, { headers });
const { event } = await res.json();
$('detail-img').src = event.images?.desktop?.src || '';
$('detail-title').textContent = event.name;
$('detail-date').textContent = formatDate(event.entryStartsAt);
$('detail-location').textContent = event.location?.name || 'TBA';
$('detail-desc').textContent = event.description || '';
$('widget').setAttribute('event-id', event.id);
}
function navigate() {
const hash = location.hash.slice(1);
$('view-events').classList.toggle('hidden', hash.startsWith('event/'));
$('view-detail').classList.toggle('hidden', !hash.startsWith('event/'));
if (hash.startsWith('event/')) {
loadDetail(hash.replace('event/', ''));
} else {
loadEvents($('venue-filter').value);
}
}
$('venue-filter').addEventListener('change', () => loadEvents($('venue-filter').value));
window.addEventListener('hashchange', navigate);
loadVenues().then(() => navigate());
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment