Touring artists carry one piece of fragile infrastructure: their calendar. If a platform's availability widget contradicts what's on their phone, the whole platform is, in their view, broken. So we couldn't fake this one.
What “sync” even means here
Three jobs that look the same from the outside but really aren't:
- Outbound feed. A read-only iCal URL you subscribe to in Google / Apple / Outlook. Easy. We add bookings + manual entries; the user's calendar pulls every few hours.
- Inbound feed. The artist's OWN events from Google / Apple / Outlook show up on ShareTheStage as “busy.” Harder, because we should not leak event titles, just dates and times.
- Two-way write-back. When a booking confirms on ShareTheStage, an event appears on their personal calendar — and if they delete it there, we know. Hardest, because now we're a state machine, not a poller.
The privacy fork in the road
The hardest design call was on the inbound feed. The naive version is: we've got an OAuth token, let's pull all events, store them, render a calendar grid with titles and locations.
If a venue can see that an artist has a dentist appointment Tuesday at 2pm, we've built an HR tool, not a booking platform.
We chose the opposite extreme: only date keys cross the boundary. Our server pulls the full event list to compute “busy or not,” collapses it into a set of ISO date strings, and stores only that. Titles, locations, attendees, and times never touch our database.
What the artist sees vs. what the venue sees
Same code path, two different audiences:
- Artist looking at their own profile. Sees the full calendar, including which events are auto-imported (greyed out, no detail) and which are ShareTheStage bookings (linked).
- Venue looking at a public profile. Sees a 17-week strip with three colors: open, limited, booked. No event names. Just the answer to “is this person likely available?”
Why this took six months
Honestly, almost none of the time was the wire-protocol stuff. iCal parsing, OAuth, webhook handling — those were the first three weeks. The rest was:
- Deletion semantics. If a booking is deleted on ShareTheStage, what happens on the artist's calendar? (Delete it. With a confirmation dialog. After we tried two other options and watched users complain.)
- Channel renewals. Google calendar webhooks expire after a week. We set up a cron that renews them and a Sentry alarm for when a channel silently dies (it happens).
- The clock-skew problem. “Friday” in Brooklyn is “Saturday” in Sydney. Bookings that cross midnight in either direction shouldn't flip the date keys. Took two rewrites of our timezone normalization layer.
- Manual entries. Not every event lives in Google. Touring artists want a quick way to block off “recording days” or “sister's wedding” without explaining it. We built a one-tap manual-block UI that lives on the dashboard.
What we'd do differently
In hindsight: the inbound feed should have been read-only by default for the first three months. Two-way write-back was the right end state but adding it on day one made the surface area too big to debug confidently. Ship the thing that can't hurt anyone first, then earn the privilege to write.
Anyway. The calendar works now. Tonight a band in Cleveland will accept a gig in Pittsburgh and the date will go grey on their phone before they put it down. That's the whole point.
