I’ve been doing integrations for most of my professional life. In my current work in the automotive industry, connecting new data sources is almost a weekly thing: pull data, normalize it, handle edge cases, repeat. Most of what I do involves pulling from different systems, though we also receive webhooks when providers support them.

Webhooks are the better tool for this kind of work. You skip the constant polling, avoid overloading APIs with requests, and don’t end up chasing stale data in circles. But I wanted to step outside my usual domain and explore a different one: online travel agencies.

The question was simple: what would it take to receive booking information from multiple OTAs (think Airbnb, VRBO, Booking.com) into a single Laravel application? The receiving and normalizing part is familiar territory for me, even if the payload shapes are different. The interesting challenge is what happens after the data lands.

The Conflict Problem

Detecting a conflict is easy. Two reservations overlap on the same property? That’s a problem. The real question is what the system should do about it.

Depending on the OTA’s API capabilities, you might be able to automate a rejection. But that needs configuration. A property owner should decide: do I want automatic resolution, or do I want to handle it manually? And if it’s manual, the system needs to make noise about it, especially when the check-in date is close.

Realistically, conflicts probably won’t happen constantly. It’s unlikely that Booking and Airbnb register a reservation at the exact same moment. More likely, you see a conflict because one sync was delayed by an hour for whatever reason. So the system should account for that: let users set their tolerance window, and escalate appropriately based on urgency.

This is where I spent most of my thinking time. Not on the code itself, but on how a real operator would want this to behave. Configuration matters more than clever algorithms here.

Watching the Watchers

In my current integrations, I track error rates over time for each provider. When something deviates from the average by a configurable percentage, I trigger an alert to Slack. It works well: if something fails occasionally, it won’t keep bothering you, but a real spike gets attention.

For OTAs, the same principle applies. You could also reference their status pages, but I’d keep that as a manual check for operators rather than automating it. Status pages can lag behind reality, and sometimes providers don’t even know they have a problem. Better to trust your own metrics and use the status page as supporting context when investigating.

Schema changes are trickier. Detecting new fields appearing is straightforward. But knowing whether a field stopped appearing because something broke versus it just not being relevant for recent bookings requires understanding patterns over time. That’s beyond what a weekend project can solve, but it’s worth thinking about.

The Setup

I kept this practical: two repos. One is a Lambda function that runs every few minutes, simulating webhook requests from different OTAs with randomized but realistic payloads. The other is a Laravel app running on my existing AWS infrastructure (covered in a previous article), just another container alongside my other small projects.

Having the Lambda fire real HTTP requests against the EC2 instance makes it more than a local exercise. It’s a small system with moving parts: external triggers, webhook reception, payload normalization, conflict detection, and monitoring. Not production-grade by any means, but enough to think through the real problems.

The structure respects what’s publicly documented about these OTA APIs. I wasn’t trying to reverse-engineer anything, just to get my head into the problem space and reason about what this kind of system needs.

On the Code

I tried to keep things the way I always work: small methods that do one thing, code that reads without needing comments to explain itself. Data access goes through repositories, business logic lives in services, and controllers stay RESTful with standard actions. I wrote feature tests covering the main webhook flows. Not exhaustive coverage, just enough to verify the important paths work correctly.

This was a weekend project, and it shows in scope. But it reflects how I approach new problems: understand the domain first, think about the user’s perspective, keep the code clean, and deploy something real rather than just theorizing.

You can check the repository here: github.com/raulprdev/channel-sync-demo