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.
| Microsoft | ||
|---|---|---|
| Created in | Google Cloud console | Microsoft Entra, in the portal of the app's cloud |
| Client type | OAuth client, type Web application | App registration, Web platform |
| Credential | Client ID and client secret | Application (client) ID and client secret |
| Registrations | One for each Cloud project you use | One 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/microsoftTo 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:
| API | Needed for |
|---|---|
| Gmail API | Mail through the API (mailAccess: "api", the default) and Gmail push. |
| Google Calendar API | Calendars (calendarEnabled). |
| People API | Contacts (contactsEnabled). |
Gmail over IMAP needs no API enabled.
Configure the consent screen
Under Google Auth Platform:
- 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. - Audience: choose who can connect. See Audience and verification.
- 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/googleLeave 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.
| Scope | Asked for |
|---|---|
openid, .../auth/userinfo.email, .../auth/userinfo.profile | Always. |
https://www.googleapis.com/auth/gmail.modify | Mail 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.freebusy | calendarEnabled. |
https://www.googleapis.com/auth/contacts | contactsEnabled. |
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
| Audience | Who can connect | What to expect |
|---|---|---|
| Internal | Users of the Google Workspace organization that owns the project. | No verification. |
| External, publishing status Testing | Up 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 production | Any 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:
cloud | Portal |
|---|---|
global (including GCC) | entra.microsoft.com |
gcc-high, dod | portal.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
authorityyou 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 permission | Asked for |
|---|---|
openid, email, profile, offline_access, User.Read | Always. Workwae reads the mailbox address from User.Read. |
Mail.ReadWrite, Mail.Send | Mail through Graph (mailAccess: "api", the default). With mailPermissions read_send: Mail.Read and Mail.Send instead; with read: Mail.Read. |
Calendars.ReadWrite, MailboxSettings.Read | calendarEnabled. Without MailboxSettings.Read, calendars sync but the mailbox time zone is unknown. |
Contacts.ReadWrite | contactsEnabled. |
Mail.ReadWrite.Shared, Mail.Send.Shared, Calendars.ReadWrite.Shared, Contacts.ReadWrite.Shared | sharedMailboxesEnabled, for shared mailboxes. The calendar and contacts ones only with those options. |
IMAP.AccessAsUser.All, SMTP.Send | mailAccess: "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.
Prepare consent
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 types | authority |
|---|---|
| Accounts in this organizational directory only (single tenant) | The directory (tenant) ID. |
| Accounts in any organizational directory (multitenant) | organizations. |
| Multitenant and personal Microsoft accounts | common, 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"
}'| Field | Meaning |
|---|---|
provider | google or microsoft. |
name | Your label for the app, up to 100 characters. Users see the name set at the provider. |
clientId, clientSecret | From the provider. The secret is encrypted and never returned. |
cloud | Microsoft only, and required (400 cloud_required): global, gcc-high, dod or china. |
authority | Microsoft only: see above. |
useConnectDomain | Sign 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
cloudorauthorityanswers 400not_microsoft. provider,cloudandclientIdcannot 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:
| Where | Error | Fix |
|---|---|---|
| Provider's page | redirect_uri_mismatch (Google), AADSTS50011 (Microsoft) | Add the exact redirect URI to the app (Google, Microsoft). |
| Provider's page | AADSTS50194, AADSTS700016 (Microsoft) | Make authority match the app's supported account types and cloud. |
| Provider's page | A consent request or an approval-required message | An administrator grants consent, or marks the app trusted. |
| Your redirect URI | invalid_client | The provider refused the client ID or secret. Check both, and whether the secret expired. |
| Your redirect URI | scope_denied | The user unticked a permission the account needs. |
| Your redirect URI, or 409 on a headless start | connect_domain_inactive | The app uses a connect domain that is not active. |
| Your redirect URI | missing_refresh_token | The 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
- Add a new secret to the app at the provider. Keep the old one.
- Replace the secret in Workwae: Replace client secret in the console, or
PATCH /v1/oauth-apps/{appId}withclientSecret. Accounts use the new secret from their next token refresh. - 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/microsoftFor 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 toapi.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). TurnuseConnectDomainoff first, after adding theapi.workwae.comredirect 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 neverWhoever 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
gmailPushwakes 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_refusedwhen 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: nullstops the watches and returns the accounts to polling.