A Scheduling Tool Turned Into a Wiretap: The Cal.com Webhook That Trusts Its Own Input
A webhook is a small promise: "when something happens here, tell that URL over there." It is one of the most useful features a SaaS product can offer, and one of the most sensitive, because whoever controls the destination URL gets a live copy of whatever the event carries. In a scheduling tool, what the event carries is people's contact details and their meeting links.
We found a flaw in Cal.com where the API that creates a webhook trusts the caller to tell it which team the webhook belongs to — and never checks whether that is true. Supply someone else's team id, and you have quietly wired their bookings to your own server. No relationship to the team, no admin rights, no interaction from anyone on the other side.
The short version
Cal.com's webhook-create API (viewer.webhook.create) accepts a teamId field and never validates it against the caller's team memberships. Any authenticated user — a free account is enough — can attach a webhook to any team by its small numeric id. From then on every booking for that team is copied in full to an attacker-controlled URL, signed with an attacker-chosen secret: booker name, email, phone, intake answers, organizer email, and the video-call URL and password. CVSS 9.6. GHSA-4fwh-xxpv-xfm6. No fix available; disclosed through CERT/CC coordination.
No fix available
This is being published through CERT/CC coordination because the vendor did not respond and there is still no patched release. The flaw is live. If you self-host Cal.com, the audit steps near the end of this post are what you can act on right now — and you should, because a planted webhook leaves no trace in the victim team's own view.
What the webhook actually leaks
Cal.com is an open-source scheduling platform. Teams use it to let clients book time, and it can push each booking to an external system in real time through webhooks so organizers can wire bookings into a CRM or their own automation. That is a legitimate, popular feature. The catch is what a booking payload contains.
For every meeting booked against a team, a webhook subscribed to that team receives the booker's full name, email, and phone number; anything they typed into custom questions or notes; the organizer's email; and the video-call URL, including its password. A webhook attached to a team is a real-time feed of that team's bookings. The only thing standing between that feed and an outsider is the assumption that only the team's own admins can create one.
The bug: one field nobody checks
Creating a webhook runs through middleware that is supposed to confirm the caller is allowed to own the webhook they are creating. It checks ownership two ways, both correctly: when the request points at an existing webhook by id, and when it ties the webhook to one of the caller's own event types.
Then there is the third path. The create request also accepts a teamId, and nothing checks that field against the caller's memberships. The handler is careful in one respect — it pins the webhook's owning user to the caller, so you cannot forge who created it. But it writes the attacker-supplied teamId straight into the database row. The webhook is now owned by the victim team.
After that, Cal.com's ordinary delivery logic takes over. When a booking event fires for a team, the platform finds every active webhook attached to that team and delivers the payload to each subscriber URL. The planted webhook looks exactly like a legitimate one to that lookup, so it gets every booking from then on.
The exploit is a single request. The victim team is identified by a small incrementing number you can guess or read off their public booking page. Send a POST to the webhook-create endpoint with that team id and your own subscriber URL and secret, and the server returns 200 OK. That is the whole attack.
Why it scores 9.6
Two things push this to the top of the scale. First, the impact crosses a tenant boundary: the people whose data leaks are the bookers and hosts, third parties who have no relationship to the attacker's account. That is a Scope-Changed condition in CVSS terms, and it is the honest way to describe a bug where a nobody account reaches into a stranger's data.
Second, the attacker chooses the signing secret. Webhook consumers verify payloads using the secret configured when the webhook was created — so if the attacker set it, the attacker can also forge their own signed messages to any system that trusts that webhook. A signed "your meeting was rescheduled, click here" is a far more convincing phish than an unsigned one. The bug is not only a data leak outbound; it is a trusted, signed channel inbound.
The pattern underneath: stubs that always say yes
The most interesting part is why the check was missing. Cal.com's open-source build ships stub versions of its permission-check service that return "allowed" for everything, because the real permission engine is closed-source and not part of the open build. The code looks like it performs role-based checks; in the open-source build, several of those checks are quietly no-ops.
The webhook teamId bug is the most damaging place that pattern surfaces, but it is not the only one. The same root cause produces cross-team read and edit of team webhooks, cross-tenant reading of a booking's attendee details, a bypass of the private-team member-list setting, and members seeing webhook secrets they should not. They all share the same fix direction: enforce the ownership check that the code appears to make.
This is worth sitting with if you self-host anything with an open-core model. The security you can read in the source is not always the security you are running. A function named like a guard is not a guard if its body was replaced with return true in the build you deployed.
The disclosure
- May 20, 2026 Identified through source review of Cal.com and reported. The webhook-create path stood out because two of its three ownership checks were real and the third — the teamId path — simply was not there.
- June 2026 Coordinated disclosure opened with CERT/CC (case reference VRF#26-06-RPFHL). GitHub Security Advisory GHSA-4fwh-xxpv-xfm6 filed.
- July 2026 Vendor had not responded on the issue. CERT/CC requested a proof-of-concept package to verify the finding and move CVE assignment forward.
- July 22, 2026 This write-up published to warn operators in the absence of a patch. CVE assignment is still pending.
What to do if you self-host Cal.com
There is no patch, so the work right now is detection and cleanup. Audit your webhook table and flag any row whose owning user is not a member of the team the webhook is attached to — that mismatch is the signature of the attack. Review every active webhook's subscriber URL against the integrations you actually expect, and remove anything pointing somewhere unfamiliar. Where you find a planted webhook, treat the affected bookings' attendee data and video-call credentials as compromised and rotate what you can.
For the full technical detail — the vulnerable code, the CVSS vector, the related issues, and the fix direction — see the full advisory.
The dangerous checks are the ones that look like they run
A missing ownership check on a single field, sitting behind middleware that appears to validate everything, does not show up in a dependency scan. It shows up when someone reads the create path and asks whether every user-supplied id is checked against the caller. That is the same question we ask when testing web applications and APIs for clients across the Charlotte, NC area and nationwide.
Get a Quote