Your own OAuth apps
Connect hosts' calendars through your own Google, Microsoft or Zoom app, so the consent screen shows your brand.
When a host connects a calendar, Google, Microsoft or Zoom shows a consent screen naming the app that's asking. By default that's coss.com's. With your own app, it's your name and logo.
You register the app with the provider, and give coss.com its client ID and secret. Each environment has its own apps, one per provider. You own the provider relationship: their review of your app, and what your privacy policy tells people. Coss.com requests the same access as it does through its own app, and no more.
With hundreds of hosts on one Google Workspace or Microsoft 365 domain, you may not need a consent screen at all: see calendar delegation.
How connections move over
- New consents in the environment use your app as soon as you add it.
- Connected hosts stay on coss.com's app until they connect again, because tokens only work with the app that
issued them. Nothing moves by itself, and nobody is disconnected. A calendar connection's
oauth_appsays which it uses. - If your app stops working, coss.com never falls back to its own.
-
In a Google Cloud project, enable the Google Calendar API, and create an OAuth client of type Web application.
-
Add these scopes to your consent screen, exactly as given:
https://www.googleapis.com/auth/calendar.events.freebusyhttps://www.googleapis.com/auth/calendar.events.ownedhttps://www.googleapis.com/auth/calendar.calendarlist.readonly
-
Add a page on your own domain as the client's authorized redirect URI, and have it forward.
-
Add the app:
const { : } = await ...({ : "google", : "1234567890-abc.apps.googleusercontent.com", : , : "https://example.com/oauth/google", : "https://example.com/calendar-connected", }); // app?.coss_callback_url is where your page forwards to.
An external Google app needs its own brand verification. Google
requires you to own every domain on the app, redirect URIs included, which is why it needs a page on your domain: you
can't verify api.coss.com. Until it's verified, Google shows a warning and limits it to 100 users. An Internal
app, only for your own Workspace, skips this.
Your privacy policy has to say how you use Google user data, and that coss.com handles it for you. Check it meets Google's API Services User Data Policy.
Microsoft
-
In Microsoft Entra, register an application. For hosts outside your own tenant, make it multi-tenant.
-
Add the delegated Microsoft Graph permissions
Calendars.ReadWrite,offline_accessandUser.Read. -
Under Authentication, add a Web redirect URI. Use
coss_callback_urlfrom step 5, or your own page. -
Under Certificates & secrets, create a client secret and note when it expires.
-
Add the app:
await ...({ : "microsoft", : "3f1c7a52-8e0b-4d6e-9a27-5c4b1e7d2f60", : , : "2028-01-01T00:00:00Z", });
For an app that's only for your own tenant, also pass tenant_id: Microsoft refuses a single-tenant app otherwise.
Zoom
-
In the Zoom App Marketplace, create a general app that users manage.
-
Set the OAuth redirect URL to
coss_callback_urlfrom step 4, or your own page. -
Add these scopes to the app, since Zoom doesn't take them in the request:
meeting:read:list_meetings,meeting:read:meeting,meeting:write:meeting,meeting:update:meeting,meeting:delete:meetinganduser:read:user. -
Add the app, with its Secret Token:
const { : } = await ...({ : "zoom", : "abcDEF123", : , : , }); // app?.zoom_event_url -
Under Event Subscriptions, subscribe to User has uninstalled or deauthorized the app, at
zoom_event_url. That address is your app's own, and its events only ever reach connections made through it.
Zoom only sends deauthorization events for apps published on its Marketplace. On an unpublished app, a host removing it in Zoom shows up later as a connection that needs reconnecting, instead of straight away.
Forwarding page
A page on your domain takes the provider's redirect, and sends the browser on to coss_callback_url with the same query
string. It needs nothing else: code and state already say everything. In most frameworks it's a single redirect:
export function GET(request: Request) {
const { search } = new URL(request.url);
return Response.redirect(`https://api.coss.com/scheduling/oauth/google/callback${search}`, 302);
}Register the page with the provider, and pass it as redirect_uri. The browser passes through api.coss.com for a
moment, then lands on the consent's return_url or the app's completion_url, with coss_calendar set to connected,
denied or invalid.
On your host page
Set a name and a privacy policy in settings, and what a host reads before connecting names you and links your policy instead of coss.com's:
await ...({
: "Acme Health",
: "https://example.com/privacy",
});When access is lost
- The provider refuses your client ID or secret, because it was deleted or has expired: the app becomes
failing, you getscheduling.oauth_app.failing, and your owners and admins get an email. Hosts aren't told, since they can't fix it. New consents are refused. Calendars can't be read, so hosts stop being offered once what's known of them is out of date, according to yourcalendar_failure_mode. - To fix it, update the secret. It's tried on a host's connection before it replaces the old one, so a bad paste
changes nothing. Calendars recover on their own, you get
scheduling.oauth_app.restored, and nobody has to reconnect. - During a long outage, a booking's calendar event is retried for a while, then given up on with
scheduling.booking.calendar_write_failed. The booking stands, and that event isn't written once the secret is fixed. - One host revokes access: that connection needs reconnecting, as with coss.com's app, and the app is fine.
await ...("calapp_01kd2m9a0b1c2d3e4f5g6h7j8m", {
: ,
: "2029-01-01T00:00:00Z",
});Owners and admins are warned 14 days before secret_expires_at.
Removing an app
An app can't be deleted while connections use it (oauth_app_in_use), since only it can refresh their access.
Disconnect them first, and new consents go back to coss.com's app. To move to a different
client ID, hosts have to consent again.
If your plan changes, a self-serve downgrade is refused until your apps are deleted. If a contract ends, your apps keep working, so no host loses their calendar, and you can still rotate a secret or delete an app, but not add one.
Last updated