Wix — ontology map
What Wix gives Kula Intelligence, what it cannot give, and how its contacts, sessions, participations, pricing plans and Cashier transactions map into the canonical model.
Source id: com.wix · Role: all-in-one — website, bookings, pricing plans and payments · Access: Wix REST APIs, API-key auth, per-site
Scope: Wix Bookings, not all of Wix
Read this first — it prevents the most common confusion on this source.
Wix is a platform, not a product. A studio's Wix account can run a website, an online store, blogs, events, forms, email marketing and more. We ingest the Bookings side only — the schedule, the people, the plans, and the money that flows through Cashier. Everything else the studio does on Wix is outside what Kula sees.
This matters because Wix's own interface and API show the operator all of it. So an operator looking at their Wix dashboard — or anyone querying Wix directly — sees totals that will not match ours, and neither number is wrong: they are counting different things. Store revenue, site traffic and non-bookings activity are theirs, not ours.
When a Wix figure doesn't reconcile, check the scope before assuming a sync problem. Say "that includes your Wix store, which Kula doesn't ingest" rather than treating the difference as missing data.
What makes Wix awkward
Almost nothing is denormalised. A session carries a schedule id and a resource list; a participation carries an event id and a contact id. Every human-meaningful field — class name, category, instructor, class time, the plan that covered the visit — is resolved by joining across other entities at transform time.
That means Wix data quality depends on which entities were pulled together. A session ingested without its services cache gets a schedule id where a class name should be.
Coverage at a glance
locations
people.location
one per location
single page, no paging
staff (staff-members)
people.staff
one per staff member
resourceId retained for session joins
resources
no canonical table
—
cache only: resolves session → instructor
services
no canonical table
—
cache only: class name + category per schedule
categories
bookings.category
one per category
kind = service
pricing_plans
commerce.plan
one per plan
products (Stores)
commerce.product
one per product
offset-paged
contacts
people.member
one per contact
offset-paged — see traps
pricing_orders
commerce.plan stub + member plan-state stub
one per membership/pass purchase
not a revenue source
ecom_orders
landed raw, not mapped
one per class booking
the booking + cancellation record — see below
transactions (Cashier)
commerce.sale + commerce.refund
one per settled charge
the canonical revenue source
sessions (calendar events)
bookings.class_session
one per session
date-windowed
participations
bookings.attendance
one per participation
The booking lifecycle — check-ins, cancellations, late cancels
Wix records more of the booking lifecycle than a first read suggests.
CONFIRMED means the member checked in to the class. It is a record of attendance, not merely of a confirmed reservation — so bookings.attendance.booking_status = 'attended' on a Wix row reflects a real check-in.
A booking is an order, and it can be cancelled before the class. When a member books a class an order is created; cancelling it before the class start produces a cancellation with its own timestamp. That lands on bookings.attendance.cancelled_at, alongside the session's starts_at.
Late cancel is derivable — cancelled within 2 hours of class start. The canonical latecancel status is not written at ingest: Wix cancellations land as cancelled, and the two-hour rule is applied at read time from cancelled_at versus the session's starts_at. The Recipes section has the query. Treat a stored booking_status = 'cancelled' as "cancelled at some point", and derive the late-cancel split yourself.
No-show behaviour is derivable from the orders, but it's a heavy call. The ecom orders — the booking records — carry what's needed to separate someone who booked and didn't show from someone who checked in. Those rows land raw in ingest.raw_record and are not mapped canonically, so answering it means reading raw orders rather than querying a table. It is possible; it is expensive. Say so before committing to it, and prefer the CONFIRMED check-in signal on bookings.attendance where that suffices.
What we cannot get
Anything outside Wix Bookings. Store orders, site analytics, blog, events, forms — a studio may run all of them on Wix and none of it reaches Kula. See Scope.
Door access / facility entry. No concept in Wix. bookings.facility_entry is empty.
Member status history before we started capturing it. Wix reports a pricing order's current state only, and the historical and superseded orders behind it are collapsed to one snapshot at ingest — so there is no lapse, pause or cancellation timeline to read back. Kula synthesises the change by detecting it itself, so from the date capture began every status, membership-type, plan and suspension change is recorded in people.member_status_event — read it with list_member_status_changes, or the status_history block on get_member_context. Each member also carries one is_baseline = true origin row recording what we first saw; that is an observation, not a change, so filter it out before counting anything. Establish the capture start before charting a series:
Accounting / general ledger. Wix Cashier gives settled payments, not a chart of accounts. accounting.* requires Xero or QuickBooks.
A single revenue endpoint that covers everything historically. Cashier transactions are date-windowed, so revenue history is only as deep as the windows that have been pulled. Older revenue that predates the pull window is simply absent — the pricing-orders endpoint cannot substitute (see the next section).
Most list endpoints are not date-filterable. Only calendar sessions and Cashier transactions honour a [from, to] window. Everything else re-pulls in full, which means a "just fetch what changed since Tuesday" question has no answer for contacts, plans, staff or products.
Appointments as a distinct concept. Wix bookings all land as calendar sessions; there is no separate 1:1 appointment feed mapped. bookings.appointment is empty for Wix.
The revenue story — read this before quoting Wix revenue
Wix has two order systems and a payments system, and they are not interchangeable.
Pricing orders are membership/pass purchases. An order records the purchase, not the stream of recurring charges against it. A weekly membership billed 59 times appears as one order. Using orders as revenue under-counts recurring income massively — historically it reported roughly a quarter of true revenue.
eCommerce orders are checkout records. They overlap Cashier and are landed raw but map to nothing.
Cashier transactions are every settled payment — recurring cycles and one-off purchases alike. This is the canonical revenue source, and the only one that maps to
commerce.sale.
So: commerce.sale for a Wix studio comes from Cashier transactions. Pricing orders survive only as plan and member-plan-state stubs that keep the commerce graph linked. If a revenue figure looks implausibly low, check whether the transactions entity has actually been pulled for the period in question.
Refunds ride on the parent SALE transaction's refunds[] array and become commerce.refund rows. Standalone REFUND and CHARGEBACK transaction rows are skipped so each gateway refund is counted exactly once — don't add them back in.
Identity and join keys
Contact
id / _id
people.member.source_external_id
Staff
id, plus resourceId
people.staff.source_external_id
Session
id
bookings.class_session.source_external_id
Participation
id, with eventId + contactId
bookings.attendance
Transaction
transactionId (not id)
commerce.sale.source_external_id
Pricing plan
id
commerce.plan.source_external_id
The cross-links that matter:
Session → class name and category goes
session.scheduleId→ service → category. Without the services pull, a session'sclass_template_idfalls back to the raw schedule id and the class name is missing.Session → instructor goes
resources[0].id→ staff viastaff.resourceId. Wix convention is that the first resource is the instructor; further mapped resources become secondary staff.Transaction → member and plan rides inline on the transaction (
order.description.items[]andwixAppBuyerId), withis_recurringand the membership category resolved from the pricing-orders cache.
Counting traps
Wix totals include products Kula doesn't ingest. The most frequent "discrepancy" on this source isn't a data problem at all — it's a Wix dashboard number that spans the store or the site alongside bookings. Check the scope before investigating a sync.
Order-based revenue under-counts. Restated from above because it is the single most expensive mistake available on Wix data. Sales come from Cashier transactions; a pricing order is one purchase, not its stream of recurring charges.
cancelled is not the same as late cancel. Wix cancellations all land as booking_status = 'cancelled'. The late-cancel split is a read-time derivation from cancelled_at against the session's starts_at — see the Recipes. Reporting all cancellations as late cancels overstates the problem substantially.
Pricing-plan events can arrive slightly out of order. Order by occurred_at, not by ingest order, when reconstructing plan state.
Questions this source can and can't answer
Can answer well
Class schedule, capacity, instructor, category, room
Who was booked on which session, who checked in (
CONFIRMED), and who cancelledLate-cancel rate — derived at read time from the cancellation timestamp against class start
True recurring revenue per member and per plan (from transactions)
Membership/pass plans, prices, intervals
Refund totals and net revenue
Retention and at-risk analysis, on booking history
Answerable, but expensive
Full no-show behaviour — derivable from the raw ecom orders, which land unmapped. Flag the cost before starting; the check-in signal on
bookings.attendancecovers most questions more cheaply.
Cannot answer from Wix alone
Anything outside Wix Bookings — store, site, blog, events (out of scope)
Walk-ins / door access (no concept)
General-ledger reporting, P&L, expenses (needs Xero/QuickBooks)
Revenue before the earliest pulled transaction window
"What changed since yesterday" for contacts, plans, staff or products (endpoints aren't date-filterable)
Recipes — what works well
Who's lapsing → list_at_risk_members
Pause-aware, bucketed by days since last visit. On Wix a "visit" means an attended class — there's no door data — and because CONFIRMED records a real check-in, the recency signal is genuine attendance rather than a booking that may never have been honoured.
Schedule performance → get_class_utilisation → get_time_slot_detail
Wix is strong here: sessions resolve to a real class name, category, instructor and room through the cross-entity caches. The heatmap first, then the per-class drill-down for a chosen weekday and hour.
If class names come back looking like opaque ids, the services entity hasn't been pulled — that's a connector problem worth flagging rather than a naming quirk.
Instructor performance → get_teacher_performance
Pass staff_ref and include_summary: true. Wix resolves the instructor via the session's first resource, so an instructor who only ever appears as a secondary resource needs include_subs: true.
Revenue → SQL on Cashier transactions
This is the recipe that most often goes wrong. Sales come from transactions; pricing orders are not revenue.
Net of refunds subtracts commerce.refund — and remember standalone REFUND and CHARGEBACK transaction rows are deliberately skipped, so don't add them back.
Before quoting any revenue figure, confirm the transactions entity actually covers the period. An implausibly low month is usually an unpulled window, not a bad month:
Retention → get_retention_curve
Works well on Wix, because Cashier transactions give real per-cycle recurring charges — which is exactly what the curve measures (billing continuity). This is the tool that would have been wrong under the old order-based revenue model.
One member's money → get_member_payments
Combines sales with unlinked gateway payments, with a record_kind column discriminating them.
Late cancels → SQL on cancelled_at vs class start
Not stored — derived. A cancellation inside 2 hours of the class start is a late cancel:
Swap date_trunc for a.member_id to find the members who repeatedly late-cancel — usually a more actionable list than the aggregate.
Two hours is the studio-agnostic default. If the operator runs a different late-cancel window, use theirs and say which you applied.
Attendance and check-in rate → bookings.attendance_guarded
booking_status = 'attended' means the member checked in, so an attendance rate is a straightforward count — no inference caveat needed. Splitting booked-but-never-honoured out fully needs the raw ecom orders, which is the expensive path; flag the cost before starting it.
What doesn't work on Wix
Door access and walk-ins (no concept in Wix), general-ledger reporting (needs Xero or QuickBooks), and anything about the studio's Wix store, site or other Wix products — out of scope, not missing. get_cac_by_cohort needs a marketing source connected for spend.
Where this lives in the code
Entity catalogue, endpoints, paging shapes
services/ingestors/wix/internal/wixclient/specs.go
Transforms + the cross-entity caches
services/ingestors/wix/internal/transform/
Revenue (Cashier transactions)
services/ingestors/wix/internal/transform/transactions.go
Sessions + participations
services/ingestors/wix/internal/transform/bookings.go
Operator-facing connect guide
Last updated
Was this helpful?