YOGO Booking recently released a REST API, and I built an open-source server-side Google Tag Manager integration for it. It polls the YOGO API for new orders, bookings, and customer registrations, then sends them to your sGTM container in near-real-time. The full source code, including an sGTM client template and a Node.js poller, is available on GitHub.

Why an API-based approach

If you run a yoga studio, fitness center, or wellness business on YOGO Booking, you have probably noticed that tracking conversions reliably is difficult.

YOGO's booking flow happens either through an embedded iframe or widget on your website, or through a direct YOGO booking link on their domain. Since YOGO owns the booking domain, you cannot set up first-party tracking with DNS records - for example, you cannot point a subdomain to a Stape server-side container on that domain. YOGO does support web GTM installation on their booking pages, but first-party server-side tracking on the booking link itself is not possible.

The YOGO API changes this. As YOGO describes it themselves: the API lets you "extract data that you can use in your own systems, or to integrate with third-party services" - including exporting customer data to your CRM or email marketing platform, and analyzing booking and attendance data in your BI tools.

I took that a step further and built a pipeline that sends YOGO data to sGTM in near-real-time.

How it works

The integration has two components: a Node.js poller and an sGTM client template.

YOGO API โ†’ Poller (Node.js) โ†’ sGTM Container โ†’ GA4 / Meta CAPI / etc.

The poller runs continuously and polls all three YOGO API endpoints every 60 seconds:

  • /orders - Paid orders with line items and customer data
  • /bookings - Class reservations with check-in and cancellation status
  • /customers - New customer registrations with booking and order history

When it finds new data, it sends it as a POST request to your sGTM container. The companion client template receives the request, validates a shared secret, and passes all YOGO data through to the container via runContainer(). From there, your tags fire - GA4, Meta Conversions API, Google Ads, Klaviyo, whatever you need.

Orders vs. bookings - an important distinction

An order is triggered when a customer makes an actual payment - buying a membership, a class pass, or a one-time drop-in. A booking is when a customer reserves a spot in a class. Bookings often happen without a new payment, for example when someone uses their existing membership or class pass.

A single order (e.g. a 10-class pass) can lead to many bookings over time, each without generating a new order. This matters for your tracking setup: orders are your conversions (revenue events), while bookings are activity events useful for analytics and remarketing.

What you get in sGTM

Three event types arrive in your container, each with all the data from the YOGO API:

Purchase events

Fired for every new paid order. Includes transaction ID, revenue, tax, line items with individual pricing, and full customer contact details. Here is what the payload looks like:

{
  "event_name": "purchase",
  "transaction_id": "10001",
  "value": 1000,
  "currency": "DKK",
  "tax": 200,
  "items": [{
    "item_id": "101",
    "item_name": "10-Class Pass",
    "price": 1000,
    "quantity": 1
  }],
  "user_data": {
    "email_address": "john@example.com",
    "first_name": "John",
    "last_name": "Doe"
  },
  "yogo_order_id": 2001,
  "yogo_paid_at": "2026-03-25T10:00:00.000Z"
}

Booking events

Fired for every new class reservation. Includes class name, start and end time, booking type (studio or livestream), check-in status, and customer data.

New customer events

Fired when a customer registers for the first time. Includes their full profile plus nested booking and order history.

All fields are available as Event Data variables in sGTM. For example: {{Event Data - value}}, {{Event Data - yogo_order_id}}, {{Event Data - user_data.email_address}}.

Technical details worth knowing

Pagination and rate limits

The YOGO API uses cursor-based pagination with a max page size of 1000 records. The poller follows the hasMore and next fields in the response exactly as the API documentation specifies. Rate limiting is 100 requests per minute per client - the poller respects the Retry-After header on 429 responses.

The bookings endpoint is different

The /bookings endpoint filters by class start time, not by when the booking was made. This means a narrow rolling time window would miss bookings for future classes and produce duplicates when a class falls inside the window. To solve this, the poller fetches a 30-day window ahead and deduplicates using a stored set of previously seen booking IDs. IDs for past classes are pruned automatically.

First-run behavior

On the very first run, the poller skips all existing records and only saves its cursor position. This prevents flooding your sGTM container with months of historical data. From the second poll onward, only new records are processed. This is a deliberate design choice - if you need historical data, you can modify the first-run logic or do a one-time API export.

Security

Every request to sGTM includes an X-SGTM-Secret header validated by the client template. The poller has zero npm dependencies - only Node.js built-ins - which eliminates supply chain risk. All credentials live in environment variables, never in code.

Deploying the poller

The poller works on any platform that can run Node.js continuously. I am running it on Railway. Railway auto-detects Node.js from the repo and runs npm start. It costs around $5/month for an always-on service like this.

Other options include Render, Fly.io, or any VPS (DigitalOcean, Hetzner). The poller is a single file with zero dependencies - it runs anywhere Node.js runs.

You will also need a server-side GTM container. I recommend Stape for hosting your sGTM container - they make the setup straightforward and handle scaling.

The setup requires four environment variables: YOGO_API_KEY, SGTM_URL (your sGTM base URL), SGTM_SECRET (shared secret matching your sGTM client), and optionally POLL_INTERVAL (defaults to 60 seconds). Full setup instructions are in the GitHub repo.

What you can do with this

  • Conversion tracking - Send YOGO purchases to GA4, Meta Conversions API, or Google Ads as proper server-side conversions with revenue data
  • Booking analytics - Track class popularity, check-in rates, cancellation patterns
  • Remarketing - Build audiences based on actual booking behavior, like "booked but did not check in" or "has not booked in 30 days"
  • Customer enrichment - Pipe new customer data to Klaviyo, your CRM, or any platform via sGTM tags
  • Revenue dashboards - Send order data to BigQuery or Looker Studio via sGTM

Limitations

The YOGO API has no webhooks, so polling is the only option. There is a delay of up to 60 seconds between an event in YOGO and it reaching sGTM. For analytics and ad conversion tracking, this is perfectly fine.

The API is read-only - you cannot create bookings or modify data through it. And it requires YOGO's Studio or Studio+App plan with the API add-on.

Get started

The entire integration is open source under the Apache 2.0 license:

If you run a studio on YOGO and want proper server-side tracking, this should save you a lot of time. Questions or feedback? Get in touch or open an issue on GitHub.