Registration & Checkout

Registration & Checkout

Everything from "I want to sign my kid up" through to "I need to swap which week we booked": tickets and capacity, payments, add-ons, coupons, refunds, rosters, and the parent's ongoing self-service. Whichever way you integrate, we own the money, the records and the compliance.

Overview

What it covers

What changes between integration options is who renders the pages and where the buyer is standing when they pay. What doesn't change is everything underneath.

We handle these no matter which option you pick

Payments, refunds and payouts
Capacity, holds and waitlists
Rosters and registration records
Clear2Join clearance collection
Receipts and transactional email
Recurring memberships and their billing
The Customer Portal for your staff
Card data — it never reaches your page or servers

Your options

Three ways in. If you'd rather see them as complete paths than as a menu, skip to the examples — or see all three side by side on Ways to integrate.

Way Status What you get What you build
Hosted Live A server-rendered storefront and booking flow on our domain, plus the org-scoped parent portal for everything after the sale. Links from your site.
Embedded / SDK Live The checkout drawer as an overlay on your page, server-rendered event-card HTML you inject, and chrome-free iframe pages. Your site and its layout; a button.
API Live — reads Public, unauthenticated JSON for events, pricing options, custom questions and checkout configuration. Your entire discovery UI. The transaction still runs through the drawer — see why.

We render it

Hosted

You send people to us. We host the pages and own the experience and the buyer's account — your integration is a link.

Hosted

Storefront & booking Live

Server-rendered, crawlable pages on our domain, scoped to your organization. Link to them from your navigation and you have a working registration business.

my-v2.thegoodgame.com/s/org/…
Your events and your logo, on our page. Everything inside the pink outline is rendered by us.
Route What it is
{WEB_BASE_URL}/s/org/{organization_id} Your event listing — every published event across every coach in your org.
{WEB_BASE_URL}/s/org/{organization_id}/event/{event_id} Event detail: schedule, venue, pricing, description, FAQs, and a Book CTA.
{WEB_BASE_URL}/s/e/{slug} The same detail page on a vanity slug, for clean marketing URLs.
{WEB_BASE_URL}/s/event/{event_id}/book The hosted booking handoff the Book CTA leads to — sign-in, athlete selection, clearance and payment. You never link this directly; the detail page does.

These render without JavaScript and are indexable — but the SEO credit lands on our domain. If you want it on yours, use event-card fragments or the event API instead.

Hosted

Customer Portal — your team's console Live

The operator side, and the part partners most often don't realise they're getting. Your own staff sign in at app-v2.thegoodgame.com and run the whole business from there. It comes with every integration option — you never build admin screens.

Build and publish events, with pricing, capacity and registration windows
Event calendar across every coach in the organization
Participant rosters, check-in, team and dorm assignments
Create and manage memberships
Coupons, promotions and add-ons
Athletes and their Clear2Join Passport status
Coaches, staff, org members and role-based permissions
Earnings, payouts, bank accounts and disbursements
Locations and venues
Organization branding and event-portal settings
Operator dashboard with revenue targets and performance
Oversight and safe-sport review queues

Everything your team creates here is what the public API and the checkout drawer then expose to your customers — one source of truth, two audiences. See it on Ways to integrate.

Hosted

Org-Scoped Parent Portal Live

Checkout is the transaction. The portal is everything after it — and that's where most of the support burden actually lives. A parent who bought from you gets a self-service account, branded as your organization, that only ever shows them your events.

GET {WEB_BASE_URL}/org/{organization_id}/manage

Add it to your account menu as "Manage my registrations" and you're done. No hosting, no build step, no data sync. It pairs with either the hosted storefront or the embedded drawer.

Status

Live in production. The portal and its /pp/org/:orgId/* API are both deployed — open {WEB_BASE_URL}/org/{organization_id}/manage with your own org id and you'll be redirected to your organization's own branded sign-in, logo and all. You need an organization id from us and at least one published event for it to have anything to show.

What a parent can do

Page Path What's there
Home /manage An "action needed" hero with outstanding requirements, upcoming events, and per-athlete status.
My Bookings /manage/bookings Every booking with your org — upcoming, past, filterable by athlete.
Booking detail /manage/bookings/{booking_id} Per-athlete registrations, outstanding requirements with a "complete now" drawer, live add-on purchase, payment summary and history, transfer requests, and cancellation requests.
My Athletes /manage/athletes Every athlete the parent has registered with your org.
Athlete detail /manage/athletes/{athlete_id} Tabs for Profile, Bookings, Clear2Join Passport, Membership, and Forms. Emergency contacts, documents, medical questions, and consent all live here.
My Memberships /manage/memberships Active membership subscriptions with self-service cancel. Hidden entirely unless your org publishes memberships.
My Wallet /manage/wallet Saved payment methods — add, remove, set default.
My Profile /manage/profile Name, email and phone changes (both verified), and account deletion.
Sign in /manage/signin Your-branded sign-in. Accepts ?redirect= so you can deep-link straight to a page behind auth.

Getting a buyer from checkout into the portal

The checkout and the portal are both served from {WEB_BASE_URL} — the same origin, no matter whose site the checkout was embedded on. So the account a buyer creates while checking out is their portal account. There's no user record to sync, no SSO handshake to implement, no identifier to map, and you never hold a credential.

Under the hood a login on either surface sets an HttpOnly refresh cookie (tgg_rt), and the other surface silently redeems it on boot via POST /auth/session — both halves are live in production, so a buyer who signed in during checkout normally lands in the portal already authenticated.

Best-effort, not guaranteed. The cookie is third-party from your page, so browsers that partition storage may withhold it until the drawer is granted Storage Access. Then the parent signs in once at the org sign-in screen. Treat a sign-in prompt as a normal outcome, not a bug — the deep links work either way.

URLs
# The portal home — put this in your account menu
{WEB_BASE_URL}/org/{organization_id}/manage

# Straight to one booking — use after a checkout completes
{WEB_BASE_URL}/org/{organization_id}/manage/bookings/{booking_id}

# Behind auth, surviving the sign-in bounce
{WEB_BASE_URL}/org/{organization_id}/manage/signin?redirect=%2Forg%2F{organization_id}%2Fmanage%2Fbookings

To get {booking_id} at the moment of purchase, listen for booking-complete on the drawer — see the JavaScript API. Branding and feature toggles are set on your organization record.

Org scope is a view filter, not the security boundary

Every read is filtered by the signed-in parent's own user id and your organization id. A parent cannot see another parent's data, and your org's portal cannot surface another organization's bookings — even if someone edits the URL. The organization id in the path is not a credential and grants nothing on its own.

Hosted

Memberships Live

Recurring revenue alongside one-off bookings. A membership is a subscription your organization publishes; families buy it once and it then covers the events you say it covers. Billing, renewals and cancellation all run on our side.

What you configure Effect
Coverage All of your events, a named list of events, or every event of a given type.
Price & billing Backed by a Stripe product; renewals and failed payments are handled for you.
Multi-athlete discount A percentage off for families enrolling more than one athlete.
Booking cap Optional maximum bookings per billing period.
Requirements & questions Its own participation requirements and custom questions, collected at signup.
Venue / activity scope Restrict a membership to one venue or activity where that makes sense.

Where each side manages it

  • Your team creates and publishes memberships in the Customer Portal.
  • Families see and cancel theirs in the Org-Scoped Parent Portal. The page is hidden entirely for organizations that don't publish any, so it never shows an empty screen.

Memberships are configured in the portal rather than over the API today — there's no public membership endpoint yet. Tell us if you need one.


Our UI in your page

Embedded / SDK

Our UI runs inside your page. You add a script tag; we render the flow. Still no payment handling and no card data in your stack, but the visitor never leaves your site.

Embedded / SDK

Checkout drawer Live

One script tag and one button attribute puts our entire registration and payment flow on your site. It opens as a full-viewport overlay on top of your existing page, so you don't need a dedicated event-detail page — trigger it from a listing, a hero CTA, a card, anywhere.

Because everything inside the drawer is served and processed by us, you stay out of payment handling entirely — card data never touches your page or your servers.

Where the card actually goes

Card fields are Stripe Elements rendered inside our cross-origin iframe, on our domain. Your page can't read them, and nothing card-shaped reaches your servers. The charge is created on our Stripe account, so we are the merchant of record on every transaction and the settlement, chargeback and card-data obligations are ours.

What that means for your own PCI paperwork depends on your setup, so confirm it with your acquirer or assessor rather than taking a number from us. We'll give them whatever they need about our side.

https://riversidesports.com/summer-camps
Your markup, your styling Rendered by The Good Game

What's inside the drawer

Ticket and session selection with live remaining capacity and per-option quantity caps
Sold-out handling with waitlist join where the event allows it
Registration windows enforced (opens-at, closes-at, manually closed)
Account step: email lookup → sign in or register, plus Google and Apple sign-in
Email OTP verification and SMS phone verification
Participants: pick saved athletes or add new ones, assigned per ticket
Eligibility gating on age range and gender policy
COPPA parental-consent flow for under-13 athletes
Clear2Join requirements inline — see Safety & Clearance
Per-event custom questions
Policy acceptance — informational, checkbox, or drawn signature
Per-athlete add-ons and coupon codes
Payment: card, Apple Pay, Google Pay, Klarna pay-in-4, and the parent's saved cards
Capacity hold with a visible countdown — 15 minutes by default, configurable per event
About and Venue tabs: description, what athletes receive, sponsors, FAQs, venue and map
Confirmation screen and emailed receipt
Your org's logo and name throughout; localized copy
Meta Pixel purchase conversion, with fbclid and _fbp forwarded from your page

Install

HTML
<!-- once, before </body> -->
<script src="https://my-v2.thegoodgame.com/embed.js" defer></script>
Easiest — declarative
<!-- auto-wired on DOMContentLoaded -->
<button data-tgg-checkout data-event-id="{event_id}">
  Register Now
</button>
Or programmatic
<button onclick="TGGCheckout.open({ eventId: '{event_id}' })">
  Register Now
</button>

Testing on staging

Swap the script src to https://staging.my-v2.thegoodgame.com/embed.js and use a staging event ID. The loader derives everything else from its own src.

Checkout must be enabled

The event needs a checkout configuration in TGG. Without one the drawer shows a "registration isn't open" message. Ask us to enable checkout for your events.

Rendering your own pricing first

Read the same bootstrap the wizard uses via the checkout-config endpoint — sessions, prices, remaining capacity, sold-out and free flags, and the registration window.

Embedded / SDK

Drawer JavaScript API Live

The loader exposes window.TGGCheckout.

Method Description
open({ eventId }) Opens the checkout drawer for the given event. eventId is the only required input.
close() Closes the drawer and returns the page to click-through. Checkout state is preserved, so re-opening resumes where the buyer left off.
init({ eventId, portalOrigin }) Optional pre-warm — creates the overlay and loads checkout assets ahead of the first click, so the drawer opens instantly. Pass eventId when the page is about one event. portalOrigin overrides the origin derived from the script src (rarely needed).
on('booking-complete', cb) Fires when a booking is created. Callback receives { bookingId } — use it to fire a pixel, record a conversion, or deep-link into the portal.
on('close', cb) Fires when the buyer dismisses the drawer.
on('view-registration', cb) Fires when the buyer clicks View registration on the confirmation screen. Callback receives { bookingId }, and the drawer closes itself first — so this is your hook for routing them somewhere. Send them to your own account page, or straight into the parent portal booking. If you don't listen for it, the drawer simply closes and the buyer is left on your page.

data-org / open({ org }) is accepted and reserved for multi-org hosts, but does not yet change behavior — pass eventId only.

JavaScript
// embed.js is deferred, so wait for load before touching TGGCheckout.
window.addEventListener('load', function () {
  TGGCheckout.on('booking-complete', function (e) {
    dataLayer.push({ event: 'registration_complete', booking_id: e.bookingId });
  });

  TGGCheckout.on('close', function () {
    console.log('checkout dismissed');
  });

  // Optional: pre-warm on an event page so the first open is instant.
  TGGCheckout.init({ eventId: '{event_id}' });
});

How the embed behaves on your page

Useful when you're debugging z-index, CSP, or analytics on a host page.

Detail Behavior
Overlay A single fixed, full-viewport, transparent <iframe id="tgg-ec-iframe"> at z-index: 2147483000, with allow="payment". The drawer and its dimming scrim are rendered inside the iframe.
When closed The iframe stays in the document but is pointer-events: none, so your page remains fully usable. It is never display: none-toggled — that's what keeps the open animation smooth.
Layout impact None. The overlay never resizes, never height-syncs, and never scrolls your page.
Messaging postMessage handshake: the embed sends tgg:ready, the loader sends tgg:open, and the embed replies with tgg:close / tgg:booking-complete. Both sides pin the exact peer origin — nothing is broadcast to *.
Redirect payments Klarna redirects away and back. The loader sets the return URL to your page, then re-opens the drawer straight onto the confirmation and scrubs its own query params via replaceState. Your URLs stay clean.
Ad attribution fbclid (from your page URL) and the _fbp cookie are forwarded into the iframe so the Purchase conversion attributes correctly.
Embedded / SDK

Event-card fragments Live

The most interesting option if you care about search: we render just the event-card grid, with no page chrome, and you inject it server-side into your own template. Google crawls your URL and sees fully-rendered listings.

https://riversidesports.com/camps?city=Fort+Worth
Your header, your hero, your filter form — our card grid dropped into the middle of it, served from your domain.
GET {WEB_BASE_URL}/s/org/{organization_id}/events/fragment
  1. Fetch it server-side when your page renders
    cURL
    curl "{WEB_BASE_URL}/s/org/{organization_id}/events/fragment?city=Fort%20Worth&sport=Tennis&size=12"
  2. Extract and inject the grid

    The response is an HTML document; the piece you want is the single element marked data-tgg-fragment="events". It carries data-tgg-total, data-tgg-page and data-tgg-size so you can render your own pagination.

    HTML (returned)
    <div data-tgg-fragment="events" data-tgg-total="192" data-tgg-page="0" data-tgg-size="12">
      <div class="grid …">
        <!-- one rendered event card per event -->
      </div>
    </div>
  3. Style it and wire the buttons

    Scope your CSS to [data-tgg-fragment], then add data-tgg-checkout data-event-id="…" to the Register buttons so they open the drawer.

Fragment filters (this is the SSR fragment vocabulary, not the JSON API's — see Event list for that one): city, market, zipCode, sport, activityId, eventType, q, minPriceCents, maxPriceCents, minAge, maxAge, genderPolicy, tag, dateFrom, dateTo, sort, page, size. Status is checked against an allowlist, so ?status= cannot reach drafts or cancelled events; ?sort=past and ?sort=all widen it to include completed. Visibility is a separate axis — the fragment forces public-only, unlike the JSON list above.

Embedded / SDK

Chrome-free iframe pages Live

If you'd rather embed whole pages than build them, the server-rendered routes have an /embed variant with our site chrome stripped, sized to drop into an iframe on your layout.

Route What it renders
{WEB_BASE_URL}/s/embed/org/{organization_id} Your event listing, no header or footer.
{WEB_BASE_URL}/s/embed/org/{organization_id}/event/{event_id} One event's detail page, no header or footer.
{WEB_BASE_URL}/s/embed/e/{slug} The same, addressed by vanity slug.

An iframe is opaque to crawlers. If search matters, prefer fragments, which put the markup in your own document.


You render it

API

Read-only, unauthenticated, no key to request — so you can call them from a server-rendered page with no secret management. Read the callout below before you forward any part of a response onward: today these endpoints return more than the catalogue.

Select fields server-side — never proxy a raw payload

These endpoints are unauthenticated and currently return three things beyond the catalogue. Participant identities: participantsPreview on event detail, and enrolledAvatars on the list, carry enrolled athletes' first and last names. Commercial totals: event detail returns revenue plus per-option collectedCents, chargedCents, coachRevenueCents, payoutCents, netCollectedCents and refundedCents. Private events: the list includes events marked private unless you pass isPrivate=false.

So build an explicit allowlist of the fields you need, server-side, and send only those to the browser. Proxying the object through — or fetching it from client-side JavaScript — publishes your enrolled families' names and your own per-event takings. We are tightening these responses; the allowlist is what makes your integration safe either way.

Exposure is configurable per organization, so a documented field can be present for one org and absent for another. Treat every field as optional — see the response envelope notes.

API

Event list Live

Every published event across every coach in your organization. Note the default includes events marked private — pass isPrivate=false for only the publicly-listed ones.

GET {API_BASE_URL}/api/v1/organizations/{organization_id}/events
cURL
curl "{API_BASE_URL}/api/v1/organizations/{organization_id}/events?size=20&sportName=Tennis"
Query param Notes
page, size Zero-indexed pagination. Defaults 0 and 10.
status Event status filter.
type Event type, e.g. Camp.
title Substring match on the event title.
sportName Sport / activity name.
location, zip Location filters.
isPrivate Include or exclude private events.

Two things about this response that will trip you up

The list is nested one level deeper than you expect, and its total is a string, not a number:

200 OK — shape only
{
  "data": {
    "total": "20",          // string — parse it before you paginate
    "data": [ /* events */ ] // note: data.data, not data
  },
  "timestamp": "2026-08-13T16:44:41.360Z"
}

List items are a summary shape, not the detail shape. They carry snake_case fields built for cards — start_date, price_cents, max_number_of_athletes, eventAlias, bannerImageUrl, sportName — and not the nested occurrences, venue or pricingPlan you get from event detail. Render listings from the list; fetch the detail endpoint for an event page.

API

Event detail Live

GET {API_BASE_URL}/api/v1/events/{event_id}

Schedule occurrences, venue, the pricing plan with every purchase option, participation requirements, registration features, organizer, activity, FAQs, and the vanity slug. Accepts a UUID or a legacy numeric id.

200 OK — application/json (abbreviated)
{
  "data": {
    "event": {
      "id": "9f8e7d6c-0000-4111-a222-333344445555",
      "title": "Summer Tennis Camp — Session I",
      "eventType": "Camp",
      "status": "published",
      "registrationModel": "per_event",
      "timeZone": "America/Chicago",
      "pricingCurrency": "USD",
      "occurrences": [
        { "date": "2026-08-03", "startTime": "14:00:00", "endTime": "21:00:00", "status": "scheduled" }
      ],
      "venue": {
        "name": "Riverside Varsity Courts",
        "address": { "line1": "123 Main Street", "city": "Fort Worth", "region": "TX", "postalCode": "76109" }
      },
      "pricingPlan": {
        "purchaseOptions": [
          {
            "id": "dddd1111-2222-3333-4444-555566667777",
            "name": "Full Day Ticket",
            "priceCents": 64500,
            "priceCentsWithFees": 70950,
            "audienceType": "participant",
            "capacity": 50,
            "registrationCount": 12
          }
        ]
      },
      "participationRequirements": { "minAge": 5, "maxAge": 18, "genderPolicy": "any" },
      "registrationFeatures": { "capacityLimit": 100, "capacityMode": "hard_cap", "registrationOpen": true },
      "slug": "summercamp"
    }
  },
  "timestamp": "2026-08-06T17:57:02.785Z"
}

Companion reads

Endpoint Returns
/api/v1/organizations/{organization_id}/events/{event_id} The same detail, org-scoped.
/api/v1/organizations/{organization_id}/events/{event_id}/products Purchasable options for the event.
/api/v1/organizations/{organization_id}/events/{event_id}/questions The event's custom registration questions.
/api/v1/organizations/{organization_id}/events/{event_id}/addons The event's purchasable add-ons.
/api/v1/events/by-slug/{slug} Resolve a vanity slug to an event. The slug is the eventAlias field on a list item; a slug that doesn't exist returns 404.

description fields contain sanitized HTML — render as rich text or strip tags. Every monetary field is an integer in cents.

API

Pricing & availability Live

The bootstrap the checkout drawer itself uses. Read it so your own page can't advertise a session the drawer will refuse.

GET {API_BASE_URL}/api/v1/ec/events/{event_id}/checkout-config
Field What it tells you
sessions[] Each purchase option with priceCents, a human timeLabel, remainingCapacity (null = unlimited), maxQuantity, and whether it can be waitlisted.
soldOut, eventCapacityRemaining Event-level availability, so a stepper can't offer more seats than exist.
free Every session is $0, so the payment step is skipped.
registrationOpen, windowOpensAt, windowClosesAt Whether registration is open right now, and the window if one is set.
ageMin, ageMax, genderRequirement Eligibility rules — enforce them in your UI to avoid a dead end at checkout.
policies[] Each policy with its full text and how it must be accepted (informational, checkbox, or drawn signature).
hasAddons, hasCustomQuestions Whether those steps will appear in the drawer.
org, description, whatAthletesReceive, sponsors, faqs, venue Everything the drawer's About and Venue tabs show, if you'd rather render it.
waitlist Whether a waitlist is offered and which occurrence a join targets.
API

Booking writes Planned

There is no server-to-server booking or payment endpoint today. This is the one real limit on an API-only registration integration, so it's called out rather than buried.

What to do instead

Read everything you need to render the catalogue, then hand off the transaction to the checkout drawer — two lines of HTML. That's deliberate for now: it's what keeps card data and clearance capture on our side rather than yours. Example D shows exactly this combination.

A partner-key registration API is on the roadmap. Tell us if you need it, because demand is what schedules it.

The parent-facing /ec/* endpoints the drawer calls are authenticated as the parent, not as a partner — they aren't a server-to-server surface and aren't documented as one.


Examples

Complete integrations

The sections above are a menu. These are four finished meals — each one takes you all the way from nothing to a live booking, so you can pick one and follow it end to end. They're alternatives to each other, not steps.

Example A · Hosted

Link to us

Live

The fastest possible launch. You add one link to your navigation and we handle browsing, registration, payment, clearance and the parent's account.

You build
One link. Optionally a second one.
Rough estimate
Under an hour
SEO credit
Our domain
  1. Get your organization ID from us

    A UUID. Everything below is scoped to it.

  2. Link your navigation at your storefront
    HTML
    <a href="https://my-v2.thegoodgame.com/s/org/{organization_id}">Camps</a>
  3. Add a "Manage my registrations" link

    Skipping it means your team fields every change request by email.

    HTML
    <a href="https://my-v2.thegoodgame.com/org/{organization_id}/manage">
      Manage my registrations
    </a>
  4. Send us your branding

    Logo, display name, the noun you use for events, and your Meta Pixel ID. We set them on your organization record.

Example B · Embedded / SDK

Checkout on your own site

Live

The most common integration. Your pages, your brand, your traffic — visitors never leave, and you still never touch payments.

You build
Your pages + a Register button
Rough estimate
Half a day
SEO credit
Your domain
  1. Add the loader once, before </body>
    HTML
    <script src="https://my-v2.thegoodgame.com/embed.js" defer></script>
  2. Mark your Register buttons
    HTML
    <button data-tgg-checkout data-event-id="{event_id}">Register Now</button>
  3. Handle completion and hand off to the portal

    Fire your analytics, then send the buyer straight into the booking so they can finish waivers immediately instead of abandoning them.

    JavaScript
    const ORG = '{organization_id}';
    const PORTAL = 'https://my-v2.thegoodgame.com';
    
    window.addEventListener('load', function () {
      TGGCheckout.on('booking-complete', function (e) {
        dataLayer.push({ event: 'registration_complete', booking_id: e.bookingId });
    
        document.getElementById('post-checkout-cta').innerHTML =
          `<a href="${PORTAL}/org/${ORG}/manage/bookings/${e.bookingId}">` +
          `Complete your athlete's requirements →</a>`;
      });
    });
Example C · Embedded / SDK

Our event cards, inside your page

Live

Example B without building the listing. We render the cards; you inject them into your own template, so the crawl credit lands on your domain.

You build
A filter form + a server-side fetch
Rough estimate
A day
SEO credit
Your domain
  1. Your visitor submits your filter form

    Sport, location, ZIP, dates, price — whatever your UI offers.

  2. Your server fetches the fragment and injects the grid
    cURL
    curl "{WEB_BASE_URL}/s/org/{organization_id}/events/fragment?city=Fort%20Worth&sport=Tennis&size=12"

    Take the element marked data-tgg-fragment="events".

  3. Wire the cards to the drawer

    Add the loader from example B and put data-tgg-checkout data-event-id="…" on the Register buttons.

Example D · API + embedded checkout

Build the whole storefront on our API

Live

Full control. You call our public endpoints, render your own listings and event pages in your own framework, and only hand off at the payment moment. This is the one for teams who already have a design system.

https://riversidesports.com/camps
Steps 1–2. Every pixel is yours; only the data came from us. Nothing is outlined because nothing here is ours to render.
https://riversidesports.com/camps/summer-tennis
Steps 3–4. Your event page, our drawer for the transaction — the only piece you don't own.
Uses
Event API + drawer
You build
Listings, filters, event pages
Rough estimate
A sprint
SEO credit
Your domain
  1. Pull the event list and render your own listing page
    cURL
    curl "{API_BASE_URL}/api/v1/organizations/{organization_id}/events?size=20&sportName=Tennis"

    Don't want to build cards? Swap this step for example C's fragment and keep the rest unchanged.

  2. Pull one event and render your own detail page
    cURL
    curl "{API_BASE_URL}/api/v1/events/{event_id}"
  3. Render live pricing and availability

    So your page can't advertise a session the drawer will refuse.

    cURL
    curl "{API_BASE_URL}/api/v1/ec/events/{event_id}/checkout-config"
  4. Hand off to the checkout drawer

    The one piece you don't build — and the reason card data never reaches you.

    HTML
    <script src="https://my-v2.thegoodgame.com/embed.js" defer></script>
    <button data-tgg-checkout data-event-id="{event_id}">Register Now</button>
  5. Send buyers to the portal afterwards

    Listen for booking-complete and deep-link into /org/{organization_id}/manage/bookings/{booking_id}, as in example B.

Ready to build?

Getting started tells you exactly what to ask us for — and there's a copy-paste email at the bottom of it. Or just write to us: an organization ID, an API key, a staging event to test against, or something still marked Preview or Planned.

Contact us