# Booking attribution (/docs/booking-attribution)



Booking attribution is an optional built-in `attribution` object, available to every venue without custom-field entitlements. It is recorded atomically when a booking is created and remains unchanged when the booking is edited, seated, cancelled or completed. Historical bookings have `attribution: null`; Karma does not reconstruct past marketing journeys.

## Channel versus marketing source [#channel-versus-marketing-source]

`bookingSource` describes how the reservation was made (`website`, `app`, `phone`, `walkin`, etc.). A purchase/order channel such as `QR` or `POS` describes how an order was placed. Neither identifies the original marketing source.

`attribution.firstTouch` records the earliest observed entry in the current venue-scoped session. `attribution.bookingTouch` records the most recent observed campaign or external referral before booking; it is not necessarily the booking page itself. Untagged internal navigation does not replace acquisition. `reportedSource` is an optional source explicitly reported by a guest or staff member. Missing UTMs do not prove a direct visit: a referrer can be unavailable.

## Capturing attribution [#capturing-attribution]

The Karma storefront automatically captures UTM parameters and external referrers before authentication. Venue-scoped session storage preserves the first touch through login and booking steps, with a maximum age of 24 hours; blocked storage falls back to memory. The Karma app captures venue-specific campaign deep links within the app session. No tracking identifier or cross-session browsing history is created.

A custom website or API integration must collect attribution itself and send it with `POST /api/v1/bookings`:

```json
{
  "locationId": 17099,
  "bookingDate": "2026-11-01",
  "startTime": "18:00:00",
  "endTime": "19:30:00",
  "timezone": "Europe/Stockholm",
  "guestCount": 2,
  "bookingSource": "website",
  "attribution": {
    "firstTouch": {
      "capturedAt": "2026-10-11T12:00:00Z",
      "utmSource": "instagram",
      "utmMedium": "social",
      "utmCampaign": "autumn",
      "landingPage": "https://gp.example/menu"
    },
    "bookingTouch": {
      "capturedAt": "2026-10-11T13:00:00Z",
      "utmSource": "google",
      "utmMedium": "cpc",
      "utmCampaign": "dinner",
      "referrer": "https://www.google.com/",
      "landingPage": "https://gp.example/book"
    }
  }
}
```

Touches support `utmSource`, `utmMedium`, `utmCampaign`, `utmContent`, `utmTerm`, `referrer` and `landingPage`, plus the required ISO `capturedAt`. Campaign and reported-source strings are limited to 500 characters. Karma strips URL queries, fragments and credentials and adds its own `recordedAt` timestamp. These observations are client-reported, not independently verified evidence of an ad click. Record only values your website actually observed.

The creation permission is sufficient to submit attribution; no generic metadata update or custom-field write is needed. A separate read-only analytics key can read it using normal booking-read permissions. Public confirmation-code lookups omit attribution.

## Embedding the booking flow [#embedding-the-booking-flow]

Use the embed script hosted by the same Karma storefront environment as your booking URL:

```html
<script async src="https://store.karma.life/booking-widget.js"
  data-karma-booking="https://store.karma.life/guiltypleasure/book"></script>
```

The script inserts the booking iframe after itself and passes the merchant page's session-scoped attribution to the frame. The bridge checks both message origin and sender. This matters because an iframe cannot read its cross-origin parent page's campaign parameters. A bare iframe only observes its own URL and browser-provided referrer; use this script or include UTMs explicitly in the iframe URL.

To preserve discovery before the booking page, install the same script on your landing/menu pages in attribution-only mode, with the same booking URL:

```html
<script async src="https://store.karma.life/booking-widget.js"
  data-karma-booking="https://store.karma.life/guiltypleasure/book"
  data-karma-attribution-only></script>
```

This captures the session without displaying an iframe. On the booking page, use the embed snippet without `data-karma-attribution-only`. Capture starts on pages where the script runs; Karma cannot see uninstrumented third-party pages.

## Walk-ins and phone bookings [#walk-ins-and-phone-bookings]

Staff can enter “How did the guest hear about you?” when creating a booking. API integrations can send `attribution: { "reportedSource": "A friend" }`. An unknown source remains unknown. QR orders without an associated booking do not gain booking attribution automatically.

## Reading the journey and revenue [#reading-the-journey-and-revenue]

Merchant booking lists show the marketing source below the booking channel. Booking details show first discovery, booking touch, reported source and expandable campaign details. Normal merchant/API booking list and detail responses include `attribution`; webhooks carrying a full booking include the same snapshot.

Link tabs using their `bookingId`, then purchases using their `tabId`. Include all tabs for a multi-resource booking and confirmed payments, subtract confirmed refunds, and avoid double-counting deposits or split payments. A booking's `tabId` can be absent once its tab is closed; retain the tab-to-booking relationship rather than relying on that active-tab convenience field. Estimated revenue is not actual paid revenue. See [Reading sales data](/docs/sales-data).

## Deployment [#deployment]

Requires database migration `20261011230000_booking_marketing_attribution.ts` from the central database repository before deploying the updated common API. Client capture alone does not enable storage in an environment running the older API.
