For companies with their own booking system

Connect your booking system to Spot

Keep your system as the source of truth. Spot shows your live availability to travellers, and either sends them to your site or takes the booking and passes it straight to you.

Two ways to connect

Option 1

Show your live times, book on your site

Spot reads your availability every 15 minutes and lists your experiences with their next open times. The Book button opens your own booking page, tagged utm_source=spottours, so it shows in your analytics.

  • ✓No commission on these bookings
  • ✓Your system stays the only place customers pay
  • ✓Publish one JSON file and Spot does the rest

Option 2

Customers book and pay on Spot

Customers pick a time, choose options and extras, and pay on Spot. Spot re-checks the time with your system just before charging, then sends the booking straight into your system.

  • ✓You get 87.5%, paid out after the experience runs
  • ✓Spot keeps 12.5%, card fees included
  • ✓Spot emails the customer their confirmation and handles refunds

Start with option 1 and switch to option 2 when you're ready. Either way you get your own company page on Spot, and your experiences show on the map, in Explore and in the AI trip planner. Plans and pricing are on listing your company.

What Spot needs from you

Your experiences

Name, description, photos, duration, prices (adult/child), group size, and any options or extras customers choose.

Your open times

Which dates and start times are bookable, plus closures, holidays and anything already booked. If one booking blocks other experiences (one kitchen, one boat), tell us.

One feed URL

Publish your experiences and open times as a Spot feed: one JSON file at a URL (spec below), which you can check yourself. No developer? Send us your booking system's API details and we'll connect it for you.

How it goes

  1. 1

    Send us your feed URL

    Check it with the checker below, then send it to us. Your experiences appear on Spot, refreshed every 15 minutes, and are hidden automatically if your feed is unreachable.

  2. 2

    You claim your page

    We send you a one-time code. Claim your company page to manage it, see every Spot booking, and connect your payout account.

  3. 3

    Optional: take bookings

    Your developer adds one endpoint (spec below). Spot bookings then land in your system automatically, labelled as Spot.

Got a code already? Claim your company page →

For developers

Publishing a Spot feed

One JSON document at a public https URL, listing your experiences and every open start time. Build it from your own system on each request (or regenerate it every few minutes). Spot reads it every 15 minutes, and with Spot checkout on, again right before charging, so keep it current. See a live example.

{
  "spotFeedVersion": 1,
  "currency": "eur",
  "experiences": [
    {
      "id": "sunset-sail",
      "title": "Sunset Sail",
      "summary": "Two hours on a classic sailing boat at sunset.",
      "description": "Longer text, plain paragraphs…",
      "theme": "sailing",
      "durationMinutes": 120,
      "photos": ["https://your-site.com/img/sail-1.jpg"],
      "highlights": ["Local skipper", "A glass of cava"],
      "price": { "adultCents": 4500, "childCents": 2500 },
      "booking": "per-person",
      "maxGroup": 12,
      "meetingPoint": { "address": "Port Vell", "latitude": 41.3765, "longitude": 2.1823 },
      "bookingUrl": "https://your-site.com/book/sunset-sail",
      "extras": [{ "id": "cava", "name": "Bottle of cava", "priceCents": 2000 }],
      "choices": [{ "key": "menu", "label": "Menu", "options": ["Fish", "Vegetarian"] }],
      "slots": [
        { "date": "2026-10-30", "time": "18:30", "spotsLeft": 8 },
        { "date": "2026-10-31", "time": "18:30", "spotsLeft": 12 }
      ],
      "active": true
    }
  ]
}

Fields

spotFeedVersion
Always 1.
currency
Lowercase currency code, and it must be your city's currency on Spot (eur for Barcelona, gbp for London…). All prices are in cents of it.
id
Your stable id for the experience: lowercase letters, digits, - and _. Never reuse one for a different experience; we use it in links and bookings.
title, summary, description
Title is required. Summary is a line or two; description can be longer.
theme
Optional, One of: food, wine-farm, sailing, cycling, nightlife, gaudi, gothic, old-city, jewish-quarter. Anything else shows under Other.
photos
https image URLs, the first is the cover. Up to 8.
price.adultCents, price.childCents
Price per adult and per child, in cents. 0 means free.
booking
"per-person" (people buy places on a shared departure) or "private" (one booking takes the whole time; priced per adult with an optional minAdults).
minAdults, maxGroup
Smallest number of adults charged (private), and the largest group.
resource
Private only. Experiences that share something (one kitchen, one boat) use the same resource name, so a Spot booking on one closes that time on the others until your feed catches up.
meetingPoint
Address, plus latitude and longitude for the map pin.
bookingUrl
Where Spot's Book button sends people when they book on your site. Spot adds utm_source=spottours.
extras, choices
Optional paid add-ons, and required questions (e.g. which menu). Their ids come back in bookings.
slots
Every bookable start: date (YYYY-MM-DD) and time (HH:MM), local time. List only times you would really sell, after your own closures, minimum notice and existing bookings. spotsLeft (per-person) hides full times. Up to 120 days ahead.
active
false hides the experience on Spot without removing it.

Check your feed

Shows exactly what Spot would list from your URL: each experience, its open times, and anything Spot would skip and why.

For developers

Receiving Spot bookings

For option 2. Add one HTTPS endpoint to your system and send us its URL. Spot POSTs a JSON event to it whenever a Spot booking is confirmed or cancelled. This page is the full spec, so you can hand it to your developer or your AI coding agent as it is.

Security

  • Spot and you share a secret (at least 32 random bytes, e.g. openssl rand -hex 32). Keep it in an environment variable such as SPOT_WEBHOOK_SECRET. Exchange it privately, never by email or chat, and never commit it.
  • Every request carries X-Spot-Signature: the hex HMAC-SHA256 of the raw request body, keyed with the secret.
  • Other headers: Content-Type: application/json, User-Agent: SpotTours-bookings/1.0.

booking.confirmed

Paid and confirmed on Spot. Create it in your system as confirmed and paid, and block the time. Dates and times are local to the experience. choices and extras use the ids we agree when we connect you; amounts are in cents.

{
  "type": "booking.confirmed",
  "eventId": "8fQ2…_booking.confirmed",
  "bookingId": "8fQ2…",
  "source": "spottours",
  "class": "your-experience-id",
  "tourTitle": "Private Paella Class",
  "date": "2026-10-30",
  "time": "11:00",
  "adults": 3,
  "children": 0,
  "choices": { "paellaType": "Seafood paella" },
  "extras": [{ "id": "rioja_red", "name": "Rioja red wine", "qty": 1, "cents": 5500 }],
  "notes": "One guest is allergic to shellfish",
  "phone": "+44 7700 900000",
  "amountPaidCents": 38500,
  "currency": "eur"
}

booking.cancelled

Cancelled on Spot, and the customer has already been refunded. Mark it cancelled and reopen the time.

{
  "type": "booking.cancelled",
  "eventId": "8fQ2…_booking.cancelled",
  "bookingId": "8fQ2…",
  "source": "spottours",
  "class": "your-experience-id",
  "tourTitle": "Private Paella Class",
  "date": "2026-10-30",
  "time": "11:00",
  "refundedCents": 38500,
  "currency": "eur",
  "reason": "Customer's flight was cancelled"
}

Rules for your endpoint

  • Verify the signature over the raw request body, before parsing JSON. A missing or wrong signature → 401, and create nothing.
  • Be idempotent: the same event can arrive more than once. Key on bookingId (and store eventIds you've handled), so a repeat never creates a second booking or a second email.
  • Mark the booked time as taken in your system, so it can't be sold twice and Spot's next availability read shows it as booked.
  • Spot has already charged the customer and emailed their confirmation. Don't charge or email them again.
  • A booking.cancelled for a bookingId you don't have: return 2xx and do nothing.
  • An unknown type: return 2xx and ignore it, so new event types never break you.
  • Reply within 10 seconds, and only return 2xx once the change is saved.

Retries

Anything other than a 2xx is retried after 5 minutes, 15 minutes, 1 hour, 3, 6, 12 and 24 hours: 8 tries over about two days. If every try fails, Spot emails you the booking so you can enter it by hand. Every booking is also emailed to you when it's made, so nothing is lost while your endpoint is down.

Example: verifying a request (Node / Next.js)

import { createHmac, timingSafeEqual } from "node:crypto";

export async function POST(req: Request) {
  const secret = process.env.SPOT_WEBHOOK_SECRET;
  const raw = await req.text(); // the raw body, before JSON parsing
  const expected = createHmac("sha256", secret ?? "").update(raw).digest("hex");
  const given = req.headers.get("x-spot-signature") ?? "";
  if (!secret || given.length !== expected.length ||
      !timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
    return new Response("Bad signature", { status: 401 });
  }
  const event = JSON.parse(raw);
  // …create or cancel the booking, idempotent on event.bookingId…
  return Response.json({ ok: true });
}

Testing it

Post a signed test event yourself. An unsigned request should get 401, the signed one 2xx, and posting it twice should still give one booking. Then send the matching booking.cancelled and delete the test booking. When it works, send us the endpoint URL and we'll send a real test from Spot.

BODY='{"type":"booking.confirmed","eventId":"test1_booking.confirmed","bookingId":"test1","source":"spottours","class":"your-experience-id","tourTitle":"Test","date":"2026-12-15","time":"11:00","adults":2,"children":0,"choices":{},"extras":[],"notes":"TEST - delete","phone":"","amountPaidCents":0,"currency":"eur"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SPOT_WEBHOOK_SECRET" -hex | sed 's/^.* //')
curl -i -X POST -H "Content-Type: application/json" \
  -H "X-Spot-Signature: $SIG" -d "$BODY" https://your-site.com/your-endpoint
Questions, or ready to connect? Send us a message →