Reference

Look-ups that apply
across all four products.

Base URLs and variables, the three auth models, how webhooks are signed and retried, what's configured on your organization record, exactly how far each surface can be customized, and what our status labels mean.

Basics

Base URLs & variables

Throughout this documentation we use the following variables. Replace them with your own values when making requests.

Variable Production Staging
{API_BASE_URL} https://api-v2.thegoodgame.com https://staging.api-v2.thegoodgame.com
{WEB_BASE_URL} https://my-v2.thegoodgame.com https://staging.my-v2.thegoodgame.com
Your team's console https://app-v2.thegoodgame.com https://staging.app-v2.thegoodgame.com
{organization_id} Your organization's unique identifier (UUID), provided by The Good Game.
{event_id} The unique identifier (UUID) of an event, returned by the events list endpoint. Legacy numeric ids also resolve.
{booking_id} A booking's UUID. Returned by the checkout booking-complete event and by the portal's bookings endpoints.
{API_KEY} Your Partner API key. Issued by us, shown once at creation. Used by clearance and staffing only.

The API prefix is /api/v1 and appears once — e.g. {API_BASE_URL}/api/v1/partner/jobs.

Auth models

Three, depending on what you're calling. You never hold a parent's credential in any of them.

Surface Auth Notes
Public event endpoints None Read-only and unauthenticated, so they can be called from a server-rendered page with no secret. But the response carries more than the catalogue — participant names, commercial totals and private events are all reachable without a key today. Read the note on the event payload before you forward any part of a raw response to a browser.
Partner API (clearance, staffing) Authorization: Bearer {API_KEY} or X-API-Key Keys are issued by us, expire if you ask them to, and are revocable. Your partner organization must be active — suspended partners get 401 on every route regardless of key validity. Scopes and the 60 req/min allowance are recorded on the key and the org, but neither is enforced yet on the staffing and clearance routes.
Parent-facing endpoints The parent's own JWT Established inside our checkout or portal and never exposed to you. This is why there's no server-to-server booking API today.

Response envelope

All API responses are wrapped in a data envelope with a timestamp:

Response envelope
{ "data": { /* payload */ }, "timestamp": "2026-08-06T17:57:02.785Z" }

Money is always cents

Every monetary field is an integer in the smallest currency unit, suffixed Cents or _cents (e.g. 64500 = $645.00 USD).

Descriptions are HTML

description fields contain sanitized HTML. Render as rich text, or strip tags for plain-text use.

Times are UTC instants

Timestamps are ISO-8601 UTC. An event also carries its own timeZone, which is the zone its schedule should be displayed in.

Every field is optional

A documented response is the most an endpoint returns. Organizations can withhold individual fields, so the same route can answer differently for two orgs. Code defensively — never require a field to be present.


Webhooks

Signing & verification Preview

Rather than polling, give us an HTTPS endpoint and we'll push signed events to it. You provide the URL; we generate the signing secret and can rotate it on request. Used by clearance and staffing.

Request shape

POST to your webhook URL
POST /your/webhook/path
Content-Type: application/json
X-TGG-Signature: t=1780000000,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b...
X-TGG-Event-Type: offer.accepted
X-TGG-Delivery-Id: 3f2a9c10-1111-4222-8333-444455556666

{
  "id": "3f2a9c10-1111-4222-8333-444455556666",
  "type": "offer.accepted",
  "data": { /* event-specific payload */ },
  "createdAt": "2026-08-06T17:12:04.001Z"
}

Verifying the signature

X-TGG-Signature is t=<unix seconds>,v1=<hex>, where the hex is an HMAC-SHA256 over the exact string <t>.<raw request body> keyed with your webhook secret. Verify against the raw body — re-serializing the JSON changes the bytes and breaks the check.

Node.js
const crypto = require('crypto');

function verifyTggWebhook(rawBody, signatureHeader, secret, toleranceSec = 300) {
  const parts = {};
  for (const kv of signatureHeader.split(',')) {
    const i = kv.indexOf('=');
    parts[kv.slice(0, i).trim()] = kv.slice(i + 1).trim();
  }

  // Reject replays.
  const age = Math.abs(Math.floor(Date.now() / 1000) - Number(parts.t));
  if (!Number.isFinite(age) || age > toleranceSec) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');

  const a = Buffer.from(expected, 'utf8');
  const b = Buffer.from(parts.v1 ?? '', 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Event types

Product Type Fires when Fires today?
Staffing offer.accepted A coach accepts your offer. Yes
Staffing offer.declined A coach declines. Yes
Staffing offer.countered A coach counters on price, or you counter theirs. Yes
Staffing offer.withdrawn You withdraw an offer. Yes
Staffing offer.expired An offer's expiresAt passes unanswered. No — nothing expires offers
Staffing payment.captured Funds captured for a job. No — no emitter
Staffing payment.payout_sent Payout sent to the worker. No — no emitter
Staffing payment.payout_failed A payout attempt failed. No — no emitter
Clearance candidate.onboarding_started An invited candidate begins onboarding. No
Clearance candidate.c2j_cleared A candidate passes clearance and is hireable. No
Clearance candidate.c2j_failed A candidate does not clear. No

Registration has no webhooks — in the embedded drawer you get the in-page booking-complete callback instead. Registration webhooks are on the roadmap alongside booking writes.

Delivery & retries

Success Any 2xx. Respond fast and do your work asynchronously — we time out at 15 seconds.
Retries Non-2xx, timeout or connection error retries at 1 min, 5 min, 30 min, 2 hours, then 6 hours. After the last attempt the delivery is parked in a dead-letter queue we can inspect and replay.
Cadence A drain runs every 2 minutes, so first delivery is near-real-time but not instant.
Idempotency Deduplicate on X-TGG-Delivery-Id (also the body's id). A retried delivery reuses the same id; assume at-least-once.

Status — read this before you build against it

The transport is live in production. Signing, the retry ladder, the dead-letter queue, the two-minute drain, the admin replay and the test-delivery button all run today. If an event is emitted, it will reach you with a valid signature and be retried properly.

Emission is per event type; the table above is the truth. The rest are in the catalog but have no emitter, so a listener for them never runs. Build against the "Yes" rows and tell us which others you need.


Configuration

Your organization record

These are set by us, not passed per request, and they apply consistently across the hosted pages, the checkout drawer, and the parent portal. Send us the values and we'll configure them.

Setting Effect
Organization name & logo Used throughout the drawer, the hosted pages and the portal shell.
Parent-portal logo override A different mark for the signed-in portal, if you want one.
Events site URL & events label Where "Browse our events" links back to, and the noun you use for them (e.g. "camps" instead of "events").
Meta Pixel ID Fires the Purchase conversion after a booking. fbclid and _fbp are forwarded from your page into the drawer automatically.
App Store / Play Store links Cross-platform CTAs pointing at the TGG mobile app.
Memberships published Derived. When your org publishes memberships, the portal's Memberships page and nav item appear; otherwise they're hidden entirely.
Hide athlete requirements Switches clearance surfaces off if your own process already covers compliance.
Disable self-cancellation Turns off self-service cancellation requests, so cancellations must come through your staff.
Feature flags Additive per-org toggles for anything else we gate.

That table is exactly what GET {API_BASE_URL}/api/v1/pp/org/{organization_id}/config returns, so you can inspect what's set for your org rather than asking. It's live in production and authenticated as a parent — call it with a signed-in session, not a partner key.

Configured elsewhere, not on that endpoint

Setting Effect
Checkout mode (Multi-Camp Cart) OFF (default): single-camp checkout — one camp per checkout. ON: multi-camp checkout — several camps from the same organization, one cart, one checkout and one charge. Set at organization level in TGG admin; ask your TGG contact to enable or disable it. Not a per-event setting or a code flag. Visual comparison · Full cart reference.
Requirement sets Which Clear2Join credentials an event demands, and how each policy must be accepted — informational, checkbox, or drawn signature. Set per organization and per event.
Checkout hold duration How long a seat is held during checkout. 15 minutes unless the event overrides it.
Fee model How platform fees are applied and who absorbs them.

Customization limits

The honest answer to "how much can I change?", surface by surface. Deeper white-labeling than what's listed is a conversation, not a config flag — but it's a conversation we have regularly.

Surface Product · Family Branding Layout & CSS SEO credit Hosted by
Hosted storefront & booking Registration · embedded Org logo, name, event content No — it's our page Our domain TGG
Parent portal Registration · embedded Logo, portal logo override, name, events label, pixel No — but surfaces toggle off (requirements, cancellation, memberships) n/a (signed-in area) TGG
Checkout drawer Registration · embedded Your logo, org name, sponsors, Meta pixel No — cross-origin iframe by design (card data never enters your page) n/a (overlay on your page) TGG
Multi-Camp Cart widget Registration · embedded accent, buttonColor, iconColor cartWidget controls position, label and checkoutLabel. It styles the widget; it cannot enable or disable Multi-Camp Cart. n/a (widget on your page) TGG SDK
Chrome-free iframe pages Registration · embedded Event and org content Partly — your surrounding layout; the frame is ours Our domain TGG
Event-card fragments Registration · embedded Your page, our cards Yes — scope CSS to data-tgg-fragment Your domain You
Public event API Registration · API Total — it's JSON Total Your domain You
Clearance in our surfaces Clearance · embedded Inherits the host surface No — or switch the surfaces off entirely n/a TGG
Clearance API Clearance · API Total Total Your domain You
Staffing API Staffing · API Total Total Your domain You (worker app is ours)

Copy isn't a column because the answer is the same everywhere: all event-facing text — titles, descriptions, policies, custom questions, FAQs — comes from your own event data on every surface. Only the surrounding UI chrome is ours, and it's localized.

Where the real flexibility is

Not in CSS overrides on our surfaces — in which option you pick. If the drawer's look is the blocker, go API for discovery and keep the drawer only for the payment moment. If our pages are the blocker, take the JSON and render everything.


Conventions

Status labels

Every status in this documentation reflects what is actually deployed, not what is planned. Where something isn't ready, the gap is named rather than glossed.

Label Means Safe to build against?
Live Shipped and running in production right now, with partners on it. Yes.
Deployed Reachable in production and working, but no partner has run it yet — so you'd be first, and the rough edges haven't been found by anyone but us. Yes, with a conversation first.
In development Not ready to be depended on, for one of two reasons: it isn't deployed yet (the URLs won't respond at all), or it is deployed and works but has named gaps and no partner has run it in production. Which one it is is always stated on the product page — Staffing is the current example: deployed and working, but no partner has run it yet. Design for it and prototype against it, but don't commit a launch date until we confirm.
Preview The plumbing exists and is reachable, but it isn't wired end-to-end — usually a manual step remains on our side. Talk to us first so you know exactly which part is manual.
Planned Designed and documented so you can react to the shape, but not built. No — but telling us you want it is what schedules it.

Questions worth answering before you start

  1. Whose domain should rank in search? If yours, you need fragments or the event API — an overlay drawer is invisible to crawlers by design, and our hosted pages earn the credit for us.
  2. Who handles "I need to change my registration"? If you don't want that queue, the parent portal isn't optional.
  3. Do you want to be in payments? If not, keep the transaction in the drawer. It's the single largest scope reduction available to you.
  4. Do you need clearance to be portable? Ours already is. Rebuilding it means re-collecting documents your users have already provided elsewhere.
  5. How much of this is your differentiator? Go API on that one; let us run the rest.

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