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, customer registrations, and membership status changes, 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 the 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
- /memberships - Membership status changes: active, paused, cancelled, ended - including why a membership ended
- /class-passes - Punch card balance and expiry, attached to purchase and booking events
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
Four 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.
Membership status events
Fired whenever a membership changes status: created, active, paused, cancelled-but-still-running, or ended. YOGO's /memberships endpoint exposes the real membership status, the reason a membership ended (cancelled by the customer, failed payment, no payment method, or cancelled by the studio), the binding period end date, and the next payment with date and amount. A failed payment is the trigger for a dunning flow ("your card failed, update it here") - typically the single most profitable email flow in a membership business.
Purchase and booking events also carry the customer's current punch card balance from /class-passes. Customers often hold several pass types at once (yoga clips, sauna clips, an unlimited period pass), so the balance comes split: yogo_clips_remaining sums valid fixed-count passes only, yogo_has_unlimited_pass flags unlimited holders so they never read as "0 clips left", yogo_class_pass_types is a flat, segmentable array of pass type names, and yogo_class_passes holds the per-pass details. Map those as profile properties in Klaviyo or your CRM and you can build "almost empty" and "expires soon" segments per pass type without reconstructing anything.
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}}, {{Event Data - yogo_membership_status}}.
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.
Cloudflare Workers is also an option since May 2026. YOGO originally served an invalid TLS certificate on the API domain, which made every request from Workers fail with a 526 error - that certificate is now fixed. The poller as written is a long-running Node process with local state, so a Workers version needs Cron Triggers for scheduling and KV or Durable Objects for cursor state. Not a drop-in port, but the platform is no longer blocked.
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 three environment variables: YOGO_API_KEY, SGTM_URL (your sGTM base URL), and SGTM_SECRET (shared secret matching your sGTM client). Optional ones include POLL_INTERVAL (defaults to 60 seconds) and BACKFILL_MEMBERSHIPS (one-time send of the current status for every live membership, so your email platform gets membership profile properties from day one). 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
- Churn and dunning flows - Trigger emails on cancellations, failed payments, and membership pauses, and segment members by real membership status
- Punch card reminders - "2 clips left" and "expires in 14 days" flows based on the actual balance from the API
- 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:
- GitHub - yogo-sgtm-client - Poller + sGTM client template
- YOGO API Documentation
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.
Disclosure: the Stape and Railway links in this post are affiliate/referral links. If you sign up through them I earn a commission at no extra cost to you, and the Railway link gives you $20 in credits on signup.