Azure Sands · Channel Sync
A unit listed in three places has three calendars and none of them talk. Someone books November 12 to 15 on Airbnb, the direct site still shows those nights open and sells them that evening. In high season the guest has already flown in, so you refund the stay, pay for a room at another property, and take the review. Small operators either block dates by hand across three dashboards or pay for a channel manager. This is the channel manager, in two workflows.

Overview
An inbound workflow runs every 30 minutes, reads the iCal export from each channel, and reconciles the property's blocked dates in a single SQL statement that upserts what is in the feed and deletes what has left it. An outbound workflow answers an authenticated webhook with an RFC 5545 calendar the channels subscribe to. They never call each other; they meet in the database, so neither depends on the other being up.
My role
- Two n8n workflows (inbound sync & outbound feed)
- Postgres schema & reconcile SQL
- iCal parser and renderer (Code nodes)
The problem
A unit listed in three places has three calendars and none of them talk. Someone books November 12 to 15 on Airbnb, the direct site still shows those nights open and sells them that evening. In high season the guest has already flown in, so you refund the stay, pay for a room at another property, and take the review. Small operators either block dates by hand across three dashboards or pay for a channel manager. This is the channel manager, in two workflows.
What I built
- Imports channel reservations as blocked dates every 30 minutes
- Removes blocks that leave a feed, so cancellations release the room
- Token protected iCal endpoint the channels subscribe to
Tools used
- n8n
- PostgreSQL
- iCal / RFC 5545
- Webhooks
- Gmail
Challenges
- Making deletion safe. Deleting blocks that vanish from a feed is what makes cancellations work, and it is also what oversells the property if a 503 gets read as an empty calendar.
- Splitting ownership of one table between staff and the sync with a source column, so the sync can freely delete its own rows and can never touch a block a human entered on purpose.
Outcome
- Eleven node inbound sync plus an outbound iCal feed served over webhook
- Upsert and delete commit as one statement, so a room is never observably free mid sync
- A failed fetch, a login page, or an empty body all count as no information: blocks are kept, never deleted
- Only manually entered blocks go back out, which stops the channels echoing blocks at each other
- Conflicts are emailed to the owner with both records, once per block, and never auto resolved
Gallery
A look inside
Project links
Private build. Ask me for a walkthrough.