Every previous post in this series assumed something I no longer get to assume: that we control the whole system. The booking engine post was about reserving a spot without racing yourself. The real-time availability post was about keeping numbers consistent across every screen a customer might be looking at. The multi-tenancy post was about isolating schools that share infrastructure. All of that lives inside Lueira's own boundaries — our database, our API, our rules.
Channel managers don't get that luxury. Lueira lets a school connect its activities to third-party activity marketplaces "de forma rápida y eficiente" — quickly and efficiently, as the feature description on the site puts it — so a ski school's inventory shows up for sale on an external platform, not just on its own Lueira storefront. That sentence is simple. What it implies underneath is not.
1. What a channel manager actually promises
The pitch to a school is straightforward: connect once, sell everywhere. Your five ski lesson slots on Tuesday morning are the same five slots whether a customer books through your own site or through a marketplace that aggregates activities from a dozen operators in the valley. For that promise to hold, Lueira has to talk to each marketplace's API, translate our internal model of activities, slots, and pricing into whatever shape that marketplace expects, and keep both sides honest about how many spots are actually left. That's the easy 20% of the feature — an adapter per marketplace. The hard 80% is what happens after the first sync, on every single change from then on.
2. Availability you don't control
The real-time availability problem we solved earlier assumed a single source of truth: our own database, our own writes, our own locks. A channel manager breaks that assumption on purpose. Now the sources of truth that matter to a customer's booking include systems we don't run at all.
Every marketplace is different. Some push webhooks the moment something changes on their side; others expect us to poll, capped by their rate limits. Some update almost instantly; others queue changes during peak load. And every one fails differently: an API that's simply down for an hour, a webhook subscription that silently stops delivering, a marketplace that acknowledges a request and then never actually applies it.
3. Handling the double-booking that still happens
A booking made on an external marketplace has to reduce availability everywhere else — Lueira's own storefront and every other connected marketplace — essentially immediately. We can't just query our own live state, because the state that just changed lives on someone else's servers. There's a window where our numbers are stale everywhere except the marketplace that just sold the spot.
func handleMarketplaceBooking(w http.ResponseWriter, r *http.Request) {
var evt MarketplaceBookingEvent
if err := json.NewDecoder(r.Body).Decode(&evt); err != nil {
http.Error(w, "bad payload", http.StatusBadRequest)
return
}
// Every marketplace booking carries an external ID. That's our
// dedupe key — webhooks can arrive twice, out of order, or after
// we've already picked up the same booking via polling.
if existing, _ := store.FindByExternalRef(evt.MarketplaceID, evt.ExternalBookingID); existing != nil {
w.WriteHeader(http.StatusOK) // already processed, ack and move on
return
}
slot, err := availability.Reserve(evt.ActivityID, evt.SlotID, evt.Spots)
if err != nil {
// Someone else already took the spot. This is the apology flow.
notifyOversell(evt)
w.WriteHeader(http.StatusConflict)
return
}
store.RecordExternalBooking(evt.MarketplaceID, evt.ExternalBookingID, slot)
channels.PushAvailabilityUpdate(evt.ActivityID, slot) // fan out to every other channel
w.WriteHeader(http.StatusOK)
}
When that conflict branch fires for real, someone's customer gets an apologetic message, a refund, and ideally an alternative slot. We haven't found a way to make that branch impossible — only ways to make it rare and handled gracefully instead of silently.
4. Keeping the source of truth in one place
The design decision that keeps this from turning into chaos is refusing to treat external marketplaces as equally authoritative. Lueira's own availability engine stays the single source of truth. Every channel integration is a synchronization layer on top of it, not a peer.
On top of that we needed a reconciliation layer, because webhooks are not a reliable delivery mechanism — they arrive twice, out of order, or not at all. Every incoming booking notification needs a stable external ID so we can dedupe against it, plus periodic reconciliation jobs comparing our view against the marketplace's.
We've been honest with ourselves that this doesn't fully close the gap. A double-booking still happens occasionally. What we built isn't a guarantee it can't happen — it's a policy for what happens when it does. Channel managers are, underneath the marketing language, a distributed systems problem wearing a travel-industry costume.
