The webhook never arrived. That's still my problem.
August 5, 2026 · 5 min read
Two clients booked me for the same hour on a Tuesday. I found out when the second one messaged to ask whether I was joining.
The event existed. It sat on one calendar, where the first client had put it, exactly as it should. It just never crossed to the other one. No error email. No warning banner. Nothing in any log I could reach. The sync tool’s status page was green the whole time, because as far as the sync tool knew, nothing had happened.
That’s the part that really bugged me. Not the missed call. The silence.
The answer everyone gives
I went and asked people who’d been running this kind of thing longer than I had. I got the same answer every time, phrased a few different ways.
It’s rare. And when it happens, it’s the provider. Google didn’t send the notification. Microsoft rate-limited us. The channel expired early. Nothing we can do about that.
Every part of that is true. You cannot make Google deliver a webhook. You cannot stop Microsoft from returning a 429. If the notification doesn’t arrive, it doesn’t arrive.
It’s also the most convenient sentence in this business. It takes the one piece of the system the vendor doesn’t control and makes it the entire explanation. Everything upstream of the missing webhook is somebody else’s fault, and everything downstream is a consequence. Case closed, nothing to fix, ship the next feature.
Why it’s not an answer
Here’s what bothered me about it.
If you never get the notification, you don’t know a change happened. If you don’t know a change happened, you don’t know you missed it. And if you don’t know you missed it, you can’t tell me.
The failure and the blindness to the failure are the same event.
A sync engine that only reacts to push notifications cannot tell the difference between “nothing changed” and “I wasn’t told about the change.” From the inside, those two look identical. Both are quiet. Both leave the queue empty. Both render a green dashboard. The engine isn’t lying to you when it says everything is fine — it has no way to know otherwise.
So the shrug isn’t just convenient. It’s the only thing you can say when your architecture has no way to find out it was wrong.
Taking the webhook back
I don’t control whether the provider delivers. I do control whether I depend on delivery to know the truth.
That’s the whole design decision. I chose to depend on those webhooks, so their failures are mine.
In practice that means the engine doesn’t take the notification as the source of truth. It takes it as a hint. Separately, on its own schedule, it re-reads both sides and compares them against what it believes it wrote. When the two disagree, that’s drift, and drift gets repaired without anyone asking. When it can’t be repaired — a revoked token, an expired authorization, an account that needs reconnecting — you get an email with a link that goes straight to the thing you need to fix.
That’s the promise, and it’s deliberately a small one. The event crosses, or you hear about it. There is no third option.
The part I actually care about
Two layers hold that up.
The first layer makes bad state structurally hard to reach. Sync loops aren’t detected after the fact, they’re rejected when you configure them, because a cyclic topology never gets saved in the first place. Every write carries a marker that says twocal wrote this, so a copy is recognized when it’s read back and never mistaken for a new event to mirror. Deletions leave tombstones, so a provider re-delivering a stale notification can’t resurrect something you cancelled. Ordering runs on a logical clock rather than wall-clock timestamps, because two calendars never agree on what time it is.
The second layer assumes the first one is wrong.
If webhooks go quiet across the fleet for longer than a full safety-net poll cycle, that pages me. If an individual pair stops making noise, that pages me. If a cluster of accounts all fail authorization at once — the shape you’d see if an OAuth app slipped into a state that expires everyone’s refresh token on a seven-day cycle — that pages me too.
I built the system so that inconsistent state should not be reachable. I also get woken up at three in the morning if it turns out to be reachable anyway.
Those two things are not in tension. That’s what a guarantee is. A promise that only holds while every dependency behaves is not a guarantee, it’s a weather forecast.
What I’m not claiming
I’m not claiming events never go missing. Providers drop things. Tokens expire. Networks do what networks do. Anyone who tells you their sync never loses a single message either isn’t running at scale or isn’t looking.
The claim is narrower and, I think, the only one worth making: the drop gets caught. Either the system fixes it before you ever knew, or it tells you it couldn’t. Silence is the failure mode I set out to kill, because silence is what put two clients in the same hour on my calendar and left me looking unprofessional.
The agreement rate between synced calendars is on the public reliability dashboard, next to the sync latency numbers, updated from production. Not a badge. The actual number, including on days I’d rather it were higher.
twocal keeps Google and Microsoft calendars in sync in seconds, verifies continuously that they still agree, and publishes both numbers where you can check them. $9/month or $90/year for the first 50 customers, locked in permanently. No per-calendar pricing. 14 days free, no card.
Tired of sync that drops events?
twocal does one thing — keeps your calendars in sync, reliably. 14 days free, no credit card.