Integrating a PMS with a building system: what actually breaks
Duplicate deliveries, out-of-order events, late corrections and WAN flaps — what breaks when a PMS meets a building system, and what we changed.
On paper, connecting a property management system to a building control system is one message: guest checked into room 412. In practice it is the messiest interface we maintain, and almost none of the difficulty is in the happy path.
These are the failure modes that actually cost us, in the order we hit them.
1. The same event arrives twice
Every PMS retries. Most retry on a timeout, which means the retry often arrives after the original succeeded — your acknowledgement was what got lost, not the event.
If check-in is not idempotent, the second delivery re-runs it. Depending on what check-in does in your building, that ranges from harmless to a room that resets while a guest is standing in it.
What we do: every event is written down before the response returns, and deduplicated against what has already been seen. A repeat delivery is recognised and acknowledged, not re-applied. This has to be the first thing built, not a fix added later, because retrofitting idempotency onto a live integration means reasoning about every event already in flight.
2. Events arrive out of order
A guest checks in, then moves rooms, and the two events overtake each other in transit. Applied in the wrong order, the building ends up with the guest in the room they left.
Ordering guarantees across the public internet are not something you can assume from a third party, and asking dozens of vendors to implement sequencing is not realistic. So the system has to be tolerant instead: events carry enough context to be applied correctly regardless of arrival order, and a late-arriving event that would contradict a newer known state is rejected rather than applied.
3. The correction that arrives after the fact
Front desks undo things. A checkout is reversed because it was the wrong room. A booking is amended after arrival. A room move is cancelled.
The naive design treats every message as current truth. That is wrong: a correction to a past event is not the same as a new event, and applying it as though it were produces state that matches neither what the PMS thinks nor what the building is doing.
Reversals need to be their own event kind, so the receiving system can tell “this never happened” from “this is happening now”.
4. The internet, on a Sunday
This is the one that changed our architecture.
Early on, events went over the internet to a central service, which applied them to the property. When the hotel’s connection dropped, check-ins stopped reaching the building — during exactly the hours when a WAN link is least likely to be looked at.
Two things came out of that:
Events buffer on site. Activity is written to a durable queue inside the property’s own database before it is sent anywhere. The link can drop, the sync agent can restart, the server can reboot — the queue survives all three and drains in order when the connection returns. Across roughly 300 properties we have not lost a check-in to a dropped network since.
The integration moved into the building. Your PMS now talks to a service on the same local network as the rooms it is describing. There is no WAN hop between “guest checked in” and “the room knows”. A hotel’s internet being down stops the dashboard updating; it does not stop the building working.
That second change had an unexpected benefit: it decoupled release cycles. When the integration lived in our cloud, adding a field to a vendor’s payload meant coordinating a release between two companies. Now, within the compatibility rules, each side evolves on its own schedule.
5. Compatibility, forever
You do not get to break an integration contract. Once a PMS vendor has shipped code that talks to you, that code is in hotels, and it will not be updated on your timetable — or possibly ever.
This shapes everything:
- Version the interface, and keep the previous major version running. An adapter migrates when its author has time, not when you release.
- Accept the old shape. Our older cloud endpoint’s payloads are still accepted unchanged against the on-premises service. Migration is a base-URL change, not a rewrite.
- Never make a field mandatory after the fact. Adding a required field breaks every integration that predates it. Add it optional and treat its absence as a valid case, permanently.
- Acknowledge with what actually happened. A
200that means “received” and a200that means “applied to the room” are different facts, and a vendor debugging their side needs to tell them apart.
6. The thing that is not technical
The most useful decision we made was to let integrators choose their own shape rather than mandating ours.
Some vendors have a single outbound webhook pipeline that everything goes through, and want one URL with a discriminator in the body. Others emit events from separate code paths and would rather post to a distinct endpoint per event kind. Both are supported and neither is second-class.
Similarly for authentication: fixed-IP integrators get an allow-list and send no header at all; dynamic-IP integrators exchange a credential for a short-lived token. Insisting on one method would have cost us integrations for no engineering benefit.
The number of systems we integrate with is a direct result of not making any of them do it our way.
If you build a PMS and want the full interface guide — payload shapes, authentication, idempotency rules and the compatibility guarantees — get in touch and we will send it along with the engineer who maintains it.