Your own OAuth apps

Create a Google Cloud OAuth client or a Microsoft Entra app registration, register it with Workwae, and use it in connect flows.

By default users sign in through Workwae's Google and Microsoft apps. Register your own app to:

  • Show your name and logo on the provider's consent screen. The grant belongs to your client.
  • Connect Gmail over IMAP and SMTP. Workwae's Google app connects Gmail only through the Gmail API.
  • Receive Gmail changes by push instead of polling.
  • Use Gmail API quota from your own Cloud project.
  • Sign in to a Microsoft cloud where Workwae runs no app.
GoogleMicrosoft
Created inGoogle Cloud consoleMicrosoft Entra, in the portal of the app's cloud
Client typeOAuth client, type Web applicationApp registration, Web platform
CredentialClient ID and client secretApplication (client) ID and client secret
RegistrationsOne for each Cloud project you useOne for each cloud

Each provider sends the sign-in back to Workwae at its redirect URI, the same in every cloud. It is not your app's return address, which goes on the redirect URI allowlist.

https://api.workwae.com/connect/callback/google
https://api.workwae.com/connect/callback/microsoft

To put the redirect URI on a domain you own, set up a connect domain.

Create a Google OAuth client

Create the project and enable the APIs

In the Google Cloud console, create a project for production use. Google's policies require a separate project for testing.

Under APIs & Services > Library, enable the APIs for the data you connect:

APINeeded for
Gmail APIMail through the API (mailAccess: "api", the default) and Gmail push.
Google Calendar APICalendars (calendarEnabled).
People APIContacts (contactsEnabled).

Gmail over IMAP needs no API enabled.

Under Google Auth Platform:

  1. Branding: app name, support email and logo as users should see them. Add your home page, privacy policy and terms links. Under Authorized domains, add the redirect URI's domain (workwae.com, or your own with a connect domain) and the domains of your own links.
  2. Audience: choose who can connect. See Audience and verification.
  3. Data Access: add the scopes for the data you connect.

Create the client

Under Clients, create a client of type Web application. Add one authorized redirect URI:

https://api.workwae.com/connect/callback/google

Leave Authorized JavaScript origins empty. Copy the client ID and the client secret as soon as the client is created, then register them with Workwae.

Scopes

Workwae asks for these scopes at sign-in, according to the account's options. Calendar and contacts scopes are asked for only when the workspace's plan includes that module.

ScopeAsked for
openid, .../auth/userinfo.email, .../auth/userinfo.profileAlways.
https://www.googleapis.com/auth/gmail.modifyMail through the Gmail API. With mailPermissions read_send: gmail.readonly and gmail.send instead; with read: gmail.readonly.
https://www.googleapis.com/auth/calendar.events, .../auth/calendar.calendarlist.readonly, .../auth/calendar.freebusycalendarEnabled.
https://www.googleapis.com/auth/contactscontactsEnabled.
https://mail.google.com/mailAccess: "imap". It replaces every scope above except the first row: an IMAP account has no calendar or contacts. It is also the only scope that allows permanent deletion.
  • Google lets users untick scopes on the consent screen. A sign-in that is missing a scope the account needs fails with scope_denied.
  • Workwae reads the mailbox address from Google's userinfo endpoint and accepts only a verified address.

Audience and verification

AudienceWho can connectWhat to expect
InternalUsers of the Google Workspace organization that owns the project.No verification.
External, publishing status TestingUp to 100 test users that you list.Refresh tokens expire after 7 days, so accounts move to reauth_required every week. Use it for development only.
External, publishing status In productionAny Google account.Google must verify the app before it uses sensitive or restricted scopes. The Gmail scopes are restricted, so verification includes a CASA security assessment. Until the app is verified, users see an unverified-app warning and at most 100 users can sign in.

Verification covers the redirect URI's domain

Google's verification asks you to prove in Search Console that you own every domain the client uses, including the redirect URI's (Google's policy). You cannot verify workwae.com, so move the redirect URI to a connect domain before you submit the app. Without verification, an External app still works for a Google Workspace organization whose administrator marks it as trusted in the Admin console, under Security > Access and data control > API controls.

A Google account holds at most 100 refresh tokens for one client. Issuing another one silently invalidates the oldest token. Reconnect an existing account with its accountId instead of connecting the mailbox again.

Create a Microsoft Entra app

Each cloud needs its own app registration, created in that cloud's portal:

cloudPortal
global (including GCC)entra.microsoft.com
gcc-high, dodportal.azure.us
china (21Vianet)portal.azure.cn

Register the app

Under App registrations, choose New registration:

  • Name: what users see on the consent screen.

  • Supported account types: who can sign in. It decides the authority you register with Workwae.

  • Redirect URI: platform Web, with the same address in every cloud:

    https://api.workwae.com/connect/callback/microsoft

Copy the Application (client) ID from the app's Overview, and the Directory (tenant) ID if the app is single tenant.

Create a client secret

Under Certificates & secrets, add a client secret. Copy its Value, not its Secret ID; the portal shows the value only once. Secrets expire: note the date and rotate the secret before it expires. Workwae accepts client secrets only for sign-in apps. Certificates are for organization connections.

Add API permissions

Under API permissions, add Microsoft Graph delegated permissions for the data you connect:

Delegated permissionAsked for
openid, email, profile, offline_access, User.ReadAlways. Workwae reads the mailbox address from User.Read.
Mail.ReadWrite, Mail.SendMail through Graph (mailAccess: "api", the default). With mailPermissions read_send: Mail.Read and Mail.Send instead; with read: Mail.Read.
Calendars.ReadWrite, MailboxSettings.ReadcalendarEnabled. Without MailboxSettings.Read, calendars sync but the mailbox time zone is unknown.
Contacts.ReadWritecontactsEnabled.
Mail.ReadWrite.Shared, Mail.Send.Shared, Calendars.ReadWrite.Shared, Contacts.ReadWrite.SharedsharedMailboxesEnabled, for shared mailboxes. The calendar and contacts ones only with those options.
IMAP.AccessAsUser.All, SMTP.SendmailAccess: "imap", instead of the mail permissions above. Exchange Online must allow IMAP and SMTP AUTH for the mailbox.

Calendar and contacts permissions are asked for only when the workspace's plan includes that module. Workwae asks for the permissions at sign-in, so a permission left out of the list is still requested. Listing them lets an administrator consent to all of them at once.

Many directories let users consent only to apps from verified publishers, or not at all. Users there cannot connect until an administrator grants consent for the directory. In your own directory, choose Grant admin consent under API permissions. For a multitenant app, complete publisher verification so users in other directories see your verified name.

Set the logo, home page, terms and privacy links under Branding & properties.

Supported account types and authority

Supported account typesauthority
Accounts in this organizational directory only (single tenant)The directory (tenant) ID.
Accounts in any organizational directory (multitenant)organizations.
Multitenant and personal Microsoft accountscommon, the default when authority is left out.

Personal Microsoft accounts (Outlook.com) exist only in the global cloud.

Register the app with Workwae

In the console, open Settings > Provider apps and choose Register app under OAuth apps. Admins and owners can register apps. With the API, POST /v1/oauth-apps needs the admin scope.

curl -X POST "https://api.workwae.com/v1/oauth-apps" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "provider": "microsoft",
  "cloud": "global",
  "name": "Example Mail",
  "clientId": "00000000-0000-0000-0000-000000000000",
  "clientSecret": "...",
  "authority": "organizations"
}'
FieldMeaning
providergoogle or microsoft.
nameYour label for the app, up to 100 characters. Users see the name set at the provider.
clientId, clientSecretFrom the provider. The secret is encrypted and never returned.
cloudMicrosoft only, and required (400 cloud_required): global, gcc-high, dod or china.
authorityMicrosoft only: see above.
useConnectDomainSign in through your connect domain instead of api.workwae.com. Default false.

The response's redirectUri is the redirect URI to register at the provider.

  • A Google app given cloud or authority answers 400 not_microsoft.
  • provider, cloud and clientId cannot be changed. Register a new app instead.
  • Workwae does not check the credentials when you register them. Connect a mailbox to test the app.

Use the app

Pass the app's id as oauthAppIds on a hosted session, or as oauthAppId for headless OAuth and refresh-token import. A Microsoft app signs in to its own cloud, whatever microsoftCloud says.

curl -X POST "https://api.workwae.com/v1/connect-sessions" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "redirectUri": "https://app.example.com/mail/connected",
  "oauthAppIds": { "google": "'"$GOOGLE_APP_ID"'", "microsoft": "'"$MICROSOFT_APP_ID"'" }
}'

Connect one mailbox through each new app to test it. Setup errors appear like this:

WhereErrorFix
Provider's pageredirect_uri_mismatch (Google), AADSTS50011 (Microsoft)Add the exact redirect URI to the app (Google, Microsoft).
Provider's pageAADSTS50194, AADSTS700016 (Microsoft)Make authority match the app's supported account types and cloud.
Provider's pageA consent request or an approval-required messageAn administrator grants consent, or marks the app trusted.
Your redirect URIinvalid_clientThe provider refused the client ID or secret. Check both, and whether the secret expired.
Your redirect URIscope_deniedThe user unticked a permission the account needs.
Your redirect URI, or 409 on a headless startconnect_domain_inactiveThe app uses a connect domain that is not active.
Your redirect URImissing_refresh_tokenThe user removes the app's earlier access in their account settings, then connects again.

The full list is in Handle the return.

Rotate the client secret

  1. Add a new secret to the app at the provider. Keep the old one.
  2. Replace the secret in Workwae: Replace client secret in the console, or PATCH /v1/oauth-apps/{appId} with clientSecret. Accounts use the new secret from their next token refresh.
  3. Delete the old secret at the provider.

PATCH also changes name, authority, useConnectDomain and gmailPush. An app cannot be deleted while accounts still use it (409 oauth_app_in_use).

Your own redirect domain

A connect domain puts the redirect URI on a hostname you own, such as connect.example.com, instead of api.workwae.com. Google's verification needs this. The domain serves only the browser steps of a sign-in through your own apps: the page before the provider, the redirect URI and the MCP key page. Workwae's own apps always use api.workwae.com.

Set the domain

In the console, open Settings > Provider apps and set the hostname under Connect domain, or use the API with the admin scope:

curl -X PUT "https://api.workwae.com/v1/settings/connect-domain" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "hostname": "connect.example.com" }'

Publish the records it returns at your DNS provider: the CNAME in records and the TXT record in ownership, which proves you control the domain's DNS. Records, statuses and checks work as for a tracking domain. Wait for status: "active", or call POST /v1/settings/connect-domain/refresh to check now.

  • The hostname must be a subdomain no workspace uses, as either a connect or a tracking domain (409 connect_domain_taken).
  • A deployment without custom domains answers 400 connect_domain_unavailable.

Add the new redirect URI at the provider

Add the redirect URI on your domain next to the existing one, so sign-ins keep working while you switch:

https://connect.example.com/connect/callback/google
https://connect.example.com/connect/callback/microsoft

For Google, also add the domain under Authorized domains and verify it in Search Console.

Switch the app

Set useConnectDomain on the app, in the console or with the API. The domain must be active (409 connect_domain_inactive). The app's redirectUri then names your domain.

curl -X PATCH "https://api.workwae.com/v1/oauth-apps/$APP_ID" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "useConnectDomain": true }'

Connect a mailbox to test it, then remove the api.workwae.com redirect URI from the provider's app.

  • Accounts already connected keep working: a token refresh sends no redirect URI.
  • If the domain stops being active, sign-ins through apps that use it fail with connect_domain_inactive. Workwae does not fall back to api.workwae.com, whose redirect URI the provider may no longer accept.
  • The domain cannot be removed or replaced while anything uses it (409 connect_domain_in_use). Turn useConnectDomain off first, after adding the api.workwae.com redirect URI back at the provider.
  • Meeting apps that sign users in and Microsoft organization connections with your own app registration take the same useConnectDomain.

Gmail push

Accounts on your own Google app can receive Gmail changes by push instead of polling. Gmail accepts a watch topic only in the Cloud project of the app's OAuth client, so you create the topic and its push subscription in that project.

Create the topic and push subscription

The app's gmailPushEndpoint is both the subscription's push endpoint and the audience of its tokens. The app's page in the console (Settings > Provider apps, then the app) shows this script with your values filled in.

PROJECT=my-project  # the project of the app's OAuth client
TOPIC=workwae-gmail
SA=workwae-gmail-push@$PROJECT.iam.gserviceaccount.com
ENDPOINT=...        # the app's gmailPushEndpoint
gcloud services enable pubsub.googleapis.com --project "$PROJECT"
gcloud pubsub topics create "$TOPIC" --project "$PROJECT"
# Gmail publishes as this account; grant publish to nobody else.
gcloud pubsub topics add-iam-policy-binding "$TOPIC" --project "$PROJECT" \
  --member serviceAccount:gmail-api-push@system.gserviceaccount.com \
  --role roles/pubsub.publisher
gcloud iam service-accounts create workwae-gmail-push --project "$PROJECT" \
  --display-name "Workwae Gmail push"
# Only the Pub/Sub service agent of the subscription's project may mint push tokens for it.
PROJECT_NUMBER=$(gcloud projects describe "$PROJECT" --format 'value(projectNumber)')
gcloud iam service-accounts add-iam-policy-binding "$SA" --project "$PROJECT" \
  --member "serviceAccount:service-$PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com" \
  --role roles/iam.serviceAccountTokenCreator
gcloud pubsub subscriptions create workwae-gmail-push --project "$PROJECT" \
  --topic "$TOPIC" \
  --push-endpoint "$ENDPOINT" \
  --push-auth-service-account "$SA" \
  --push-auth-token-audience "$ENDPOINT" \
  --ack-deadline 30 \
  --message-retention-duration 600s \
  --min-retry-delay 10s --max-retry-delay 120s \
  --expiration-period never

Whoever runs it needs iam.serviceAccounts.actAs on the push service account, for example through roles/iam.serviceAccountUser.

Set gmailPush

Save the topic and service account on the app's page in the console, or with the API:

curl -X PATCH "https://api.workwae.com/v1/oauth-apps/$APP_ID" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "gmailPush": {
    "topic": "projects/my-project/topics/workwae-gmail",
    "serviceAccount": "workwae-gmail-push@my-project.iam.gserviceaccount.com"
  }
}'
  • Saving gmailPush wakes the app's accounts so it applies at once. Saving it again retries a watch that Gmail refused.
  • A refused watch leaves the account polled. Its runtime detail shows gmail_watch_refused when the topic is in another project or Gmail may not publish to it.
  • Workwae accepts a push only with a token for this app's endpoint, signed for its service account.
  • gmailPush: null stops the watches and returns the accounts to polling.

On this page