Skip to content

Where your bookings come from

Every booking records its channel: your Instagram bio link, a counter QR code, your Google listing, your website, your newsletter. Share tagged links, and Channels shows each one’s bookings, money taken and no-shows.

A link sets its channel with ch: book.tuppence.ai/bellas-barks?ch=instagram. Your page keeps ch through the booking steps and staff pages. The channel is fixed when the booking is made.

On your page or widget, the first rule that applies wins:

  1. The link's cha known or set-up code
  2. Google tokenrwg_token → google_reserve
  3. Widgetno ch → website
  4. Referring siteinstagram, google_link…
  5. Nowherelink
RuleDetail
1. The link’s chOne of Tuppence’s codes, case-insensitive (?ch=Instagram → instagram), or one you’ve set up. Anything else is other, with the raw value in channel_raw (tiktok-ad-7).
2. Reserve with GoogleGoogle’s rwg_token means google_reserve (not live yet).
3. Your websiteWidget bookings without ch are website.
4. The referring siteInstagram → instagram; Facebook, Messenger → facebook; TikTok → tiktok; Bing → bing; Linktree → linktree; WhatsApp → whatsapp; Apple Maps → apple; Google search, Maps and short links → google_link (not Gmail or Docs); Gmail, Outlook or Yahoo webmail → email. Other sites are other.
5. NowhereTyped, bookmarked, or from an app that sends no referrer: link.

Only the referring host is kept, in referrer (l.instagram.com); paths and queries can hold personal details. Your page passes it on as ?ref= through later steps.

At the desk or through the API, a booking has the channel you set (Walk-in sets walk_in) or none. The API stores it verbatim, so send instagram, not Instagram, which would count separately.

source records how it was booked (online, widget, dashboard, api, agent). A client from your bio link booking on your page is source: online, channel: instagram.

CodeCalledIts bookings
linkYour page’s linkYour Share link, its service and staff links, and visits with no referrer
qrQR codeScans of your QR code from Share
websiteYour websiteYour site’s widget (widget is an alias)
emailEmail signatureYour signature’s Book button; webmail links
google_linkGoogle Business ProfileYour Google listing’s booking link; Google search and Maps
instagramInstagram bioYour bio link; Instagram visits
instagram_storyInstagram storyA story’s link sticker
facebookFacebookYour Page’s button; Facebook and Messenger
appleApple Business ConnectThe Book action on your Apple Maps place card; Apple Maps
whatsappWhatsApp BusinessYour greeting or away message; WhatsApp
tiktokTikTokYour profile’s website; TikTok
linktreeLinktreeYour Linktree
bingBingVisits from Bing
google_reserveReserve with GoogleGoogle’s own Book button, once live
nudgeYour marketingThe button in your marketing messages
agentAI agentsA client’s AI assistant booking on your page
waitlistThe waitlistA waitlist offer the client took
walk_in, phoneWalk-in, PhoneYour bookings: Walk-in on the calendar, or phone via the API
seedExample bookingsThe test-mode example week
otherOtherUnknown ch or unlisted site

Share has a link for each of the first twelve; Channels suggests the ones you haven’t used.

LinkWhat it is
Your linkYour page with ?ch=link (one per location), plus direct links to each service and person: …/bellas-barks/book?service=svc_…&ch=link.
QR codePNG or SVG, for your page or one service. Uses a short link (book.tuppence.ai/w/…, ch=qr) that survives address changes and counts scans (Scanned 12 times).
Your websiteThe widget’s tag, with data-ch="website".
Email signatureA Book with … button, ch=email.
Where your clients already areCards with steps and a link for Google Business Profile, Instagram bio and story, Facebook, Apple Business Connect, WhatsApp Business (with a link auto-reply), TikTok and Linktree.

Copying a link (or downloading the QR code) sets its channel up: Set up, then Live after the first booking. This needs permission to edit the booking page; the links work for anyone.

Give a newsletter, flyer or advert its own channel so its bookings aren’t other.

How
POST /v1/booking_channelscode (1–40 lower-case letters, digits, _ or -, starting with a letter or digit), kind (link, qr, email, instagram, other…) and label. Add ?ch=<code> to its links (case-insensitive).
The same code againUpdates it: a new label renames; for your own, a new kind changes it. other and none are reserved.
One of Tuppence’sOnly code (what Share does on copy).
A QR code of your ownPOST /v1/booking_page/short_links with your ch (qr_window) returns a stable short link and sets up QR code (qr_window).
In your widgetdata-ch="spring-offer" instead of website.

Setting a channel up needs booking_page:write. Custom channels are created via the API only; Channels lists them with the rest.

Channels (after Booking reports in the menu) is for the team, not staff logins:

  • The days. From/To, or 7, 30 or 90 days to the To day, plus location if you have several. Defaults to 30 days; bookmarkable via the URL. Bookings count on the visit day at their location, not the booking day, so future days show what’s ahead.
  • Four figures. Bookings (completed, didn’t come, cancelled), Taken on them, Didn’t come, and From your channels (5 of 6: you made the rest).
  • By channel. Status, bookings, money taken, no-shows (25.00% (1 of 4)) and latest booking date; then Not from a channel and All bookings. Every count links to those bookings in the bookings list (which also shows uncounted holds and requests). Cards on a phone.
  • Google finds you by name, address and phone. Your location’s three, what’s missing, and Check your location details. Google needs an exact match with your Business Profile, or it shows no Book button, or someone else’s.
  • Put your link in more places. Unused places in Share’s order, with steps and Get the link.
StatusstatusMeans
Not set upsuggestedA place you haven’t used yet.
Set upset_upLink handed out; no booking yet.
LiveliveHas bookings; last_booking_at is the latest. A request counts when made, not again on approval.
Needs attentionerrorFor partner integrations, not built yet.

Live is permanent.

FigureCounts
BookingsAll bookings for those days except holds, waiting requests, declined and lapsed. Cancelled ones count and are labelled.
TakenPaid towards the price, by card and at the desk, less refunds. Excludes tips, late-cancellation and no-show fees, gift vouchers, packages and memberships (paid when sold).
Didn’t comeNo-shows as a share of completed or no-show visits, to two decimal places; a dash if none.
From your channelsBookings with a channel, including other.

These match the booking reports’ Where bookings came from figures.

On Bookings, Came from filters by channel or Not from a channel (kept in the URL). Each booking shows how and where from (The booking page · Instagram bio); other shows its raw value (Other · tiktok-ad-7) or host. Its history starts Booked online from Instagram bio.

API: GET /v1/bookings?channel= takes an exact code, other or none, plus ?from=&to= and ?location=. Bookings include channel, channel_raw (when other) and referrer.

Every marketing message (rebooking nudge, birthday message, campaign, last-minute slot) has a button that books with ch=nudge (Your marketing on Channels). Marketing counts them per campaign; See them opens the bookings list at channel=nudge. No setup needed.

GET /v1/booking_channels (bookings:read) returns Channels as data. Parameters: ?from=&to= (YYYY-MM-DD visit days; default last 30, max 366) and ?location=.

Field
channelsYours, unused suggestions (status: "suggested", id: null) and any others in those bookings, busiest first, each with stats: bookings, completed, no_shows, cancelled, revenue (minor units of the summary’s currency), no_show_rate ("25.00", or null with no outcomes).
without_channelBookings without one.
totalsAll of them, matching the bookings list.

Create a channel, book from it, and check that day’s channels, on an account with a staffed service (Set up bookings):

Terminal window
api() { # GET a path, or POST it with a body, using your key and a fresh Idempotency-Key
if [ $# -eq 1 ]; then
curl -sS "https://api.tuppence.ai$1" -H "Authorization: Bearer $TUPPENCE_SECRET_KEY"
else
curl -sS "https://api.tuppence.ai$1" -H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" -d "$2"
fi
}
# A channel of your own, before its link goes out: your newsletter's links carry ?ch=newsletter.
api /v1/booking_channels '{"code": "newsletter", "kind": "email", "label": "Newsletter"}' \
| jq -r '"\(.label) \(.id | startswith("chan_"))"' # Newsletter true
# One of Tuppence's needs only its code.
api /v1/booking_channels '{"code": "instagram_story"}' | jq -r '"\(.kind) · \(.label)"' # instagram · Instagram story
# A newsletter reader rings to book: at the desk, name the channel they came from.
location=$(api /v1/locations | jq -r '.data[0].id')
service=$(api "/v1/services?status=active&limit=1" | jq -r '.data[0].id')
starts_at=$(api "/v1/availability/next?service=$service&location=$location" | jq -r .starts_at)
booking=$(api /v1/bookings "{\"location\": \"$location\", \"items\": [{\"service\": \"$service\"}],
\"starts_at\": \"$starts_at\", \"client\": {\"name\": \"Nia Clarke\", \"email\": \"nia@example.test\"},
\"channel\": \"newsletter\"}")
id=$(echo "$booking" | jq -r .id)
day=$(echo "$booking" | jq -r .local_date)
# Where that day's bookings came from: the newsletter is live.
api "/v1/booking_channels?from=$day&to=$day" \
| jq -r '.channels[] | select(.code == "newsletter") | "\(.label) \(.status)"' # Newsletter live
# Its count is a list: the bookings list, by channel.
api "/v1/bookings?channel=newsletter" | jq --arg id "$id" '[.data[].id] | index($id) != null' # true
Call
GET /v1/booking_channelsBookings by channel over a date range (bookings:read).
POST /v1/booking_channelsSets a channel up: code, plus kind and label for your own (booking_page:write).
POST /v1/bookingschannel: the client’s source.
GET /v1/bookings?channel=Bookings from a channel: its code, other, or none.
POST /v1/booking_page/short_linksA QR short link with its ch; a custom ch sets the channel up.

The Bookings reference has every field.

Reserve with Google. Google’s Book button needs Tuppence in Google’s partner programme (a person applies; Google reviews), so no bookings come from it yet. Arrivals are handled: a visit with rwg_token is google_reserve, and the token is stripped from the address, held in the tab and kept with the booking (page only, not the widget). It never appears in the API or logs; a malformed token is dropped and the booking proceeds. 30-day retention and conversion reporting come with the integration. Until then, use Share’s Google Business Profile card (google_link).

Native Book buttons elsewhere. Not built: Instagram’s and Facebook’s Book now (Meta chooses providers; a person asks), Apple’s Book on your Maps card (a person registers Tuppence), or booking inside WhatsApp. Use your link there today. external_ref and Needs attention are reserved for these and unused.

Docs assistant

Open as a page
Loading the assistant…