Connect flows

Connect Google, Microsoft and IMAP mailboxes through a hosted page, your own UI or an existing refresh token, and reconnect them.

Every mailbox Workwae reads is an account. This page covers the ways a user connects one, the OAuth apps behind the sign-in, and how to reconnect an account whose grant stopped working.

Choose a flow

FlowYou buildUse it when
Hosted connect sessionNothing: Workwae hosts the provider choice, the OAuth hand-off and the IMAP form.You want the shortest path, or need IMAP mailboxes.
Headless OAuthYour own provider picker. Workwae returns the provider's authorization URL and handles the callback.The connect step is part of your own UI.
Refresh-token importYour own OAuth flow. You hand Workwae the refresh token.You already hold refresh tokens and want no user interaction.
Password IMAP accountYour own form for address, password and servers.The mailbox is neither Google nor Microsoft.
Organization connectionAn administrator's one-time authorization.You add every mailbox of a Google Workspace or Microsoft 365 domain.

The browser flows end the same way: the user returns to your redirectUri, which must be on the allowlist. A hosted session may leave it out; the user then ends on a Workwae confirmation page and you read the outcome from the session.

Redirect URI allowlist

The tenant setting redirectUris lists where connect flows may send the user back. Edit it on the console's Settings page or with PATCH /v1/settings (admin scope).

  • Up to 50 entries, each an absolute https URL. Plain http is accepted only for localhost and 127.0.0.1.
  • A redirect URI is allowed when its origin and path exactly match an entry. The query string and fragment are yours to vary.
  • Anything else answers 400 redirect_uri_not_allowed.
  • A session whose address is removed from the list afterwards stops working.

Hosted connect sessions

Create a session and send the user's browser to its url before expiresAt.

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",
  "providers": ["google", "microsoft"],
  "externalRef": "user_42",
  "state": "csrf-token",
  "options": { "calendarEnabled": true }
}'
FieldMeaning
providersWhat the page offers: google, microsoft, and imap (the IMAP/SMTP form for any other mailbox). Defaults to Google and Microsoft.
redirectUriWhere the user returns with the outcome. Optional: without it they see a Workwae confirmation page, which needs no allowlist entry.
stateEchoed back to the redirect URI unchanged.
externalRefYour identifier, stored on the account.
loginHintThe address the user should connect. It prefills the provider sign-in and the IMAP form.
accountIdReconnect this existing account instead of creating a new one.
oauthAppIdsYour own OAuth apps to use instead of Workwae's, per provider (google, microsoft).
microsoftCloudThe Microsoft cloud for sign-in with Workwae's app. Ignored for your own app.
optionsSettings for the new account, below.
mcp{ scopes, name? }: show the user an MCP key for the new account before they continue. See MCP for your users.
ttlSecondsHow long the link works: 60 seconds to 24 hours, one hour by default. A session can be completed once.

options sets up the account:

  • calendarEnabled, contactsEnabled: also sync calendars and contacts. Their scopes are asked for only when your plan includes the module.
  • bodyStorage: whether message content is stored (see Read mail).
  • mailAccess: api (default) or imap, which reads the mailbox over IMAP with the OAuth grant. See IMAP with OAuth.
  • sharedMailboxesEnabled: Microsoft only; lets the account open the shared mailboxes its user can access.

With mcp, the session needs the admin scope. Once completed it names the minted key as mcpKeyId; when the account already holds 25 keys, it mints none and answers mcpKeySkipped.

Errors creating a session

StatusReason
400redirect_uri_not_allowed; oauth_app_mismatch (an app is for the other provider); google_imap_requires_own_app; storage_unavailable (bodyStorage cache or full with no object store); org_connection_account or shared_mailbox_account (accountId cannot be reconnected this way)
402billing_past_due: payment is past due beyond the grace period (new accounts only)
403plan_limit_storage: the plan does not include body storage
409account_mismatch (providers leaves out the account's sign-in), mcp_key_limit
422unknown_account, unknown_oauth_app

Handle the return

The browser comes back to your redirect URI with the outcome in the query string:

?status=success&account_id=<id>&state=<your state>
?status=error&error=<code>&state=<your state>

Check state against what you sent, then use the account id. The account.connected event announces the same account.

Without a redirectUri, read the session instead. GET /v1/connect-sessions/{sessionId} returns its status (pending, completed or failed), the error of a failure, and the accountId and email it connected.

curl "https://api.workwae.com/v1/connect-sessions/$SESSION_ID" \
  -H "Authorization: Bearer $WORKWAE_API_KEY"
errorMeaning
access_deniedThe user declined the consent screen.
scope_deniedThe user unticked a permission the account needs.
session_usedThe session was already completed.
session_expiredThe link was opened after ttlSeconds.
provider_not_allowedThe session does not offer the provider that was picked.
plan_limit_accountsYour plan's account limit is reached. Delete an account or raise the plan, then send a new link.
account_existsThe mailbox is already connected. Create a reconnect session (accountId) to replace its sign-in.
account_mismatchA reconnect signed in with a different mailbox than the account being reconnected.
account_not_foundThe account being reconnected was deleted meanwhile.
missing_refresh_tokenThe provider issued no refresh token, so the account could not be kept signed in. Try again; the user may need to remove the app's earlier grant first.
token_exchange_failedThe provider refused to exchange the sign-in for tokens.
identity_failedThe provider did not say which mailbox signed in.
unknown_oauth_appYour own OAuth app for this provider was removed before the sign-in finished.
google_imap_requires_own_appGoogle over IMAP was requested through Workwae's app, which connects Gmail through the Gmail API only. Use your own Google OAuth app.
billing_past_dueYour payment is past due beyond the grace period, so no new account is connected.
provider_errorThe provider reported an error of its own.
internal_errorSomething failed on Workwae's side; repeat the attempt with a new link.
  • Treat a code not listed here as a failed attempt: new codes may be added.
  • If the return address is no longer on the allowlist, the user sees an error page instead, since there is nowhere safe to send them.

Headless OAuth

With your own provider picker, request the authorization URL for the chosen provider and redirect the user to it. The callback, the account creation and the return to your redirect URI work as for hosted sessions.

curl -X POST "https://api.workwae.com/v1/oauth-authorizations" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "provider": "microsoft",
  "redirectUri": "https://app.example.com/mail/connected",
  "state": "csrf-token"
}'

It takes provider (google or microsoft) and the same redirectUri, state, externalRef, accountId, microsoftCloud and options as a session, with one oauthAppId instead of oauthAppIds.

Open the URL in the user's own browser

authorizeUrl is a page on the API origin that names your organization and the provider; the user continues to the provider with its button. Continuing works once and binds the sign-in to that browser with an HttpOnly cookie. A callback arriving in any other browser gets an error page and connects nothing.

Import a refresh token

When your own OAuth app already holds a refresh token with the mail (and calendar) scopes, connect the account without a browser. Name the app with oauthAppId (registered below); omit it for tokens issued to Workwae's app. The account's address is the one the provider reports for the grant.

curl -X POST "https://api.workwae.com/v1/accounts" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "provider": "google",
  "refreshToken": "1//0g...",
  "oauthAppId": "6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b",
  "externalRef": "user_42"
}'

The token is checked with the provider before the account is created. Refusals answer 400 with invalid_grant (revoked, expired or never valid), invalid_client, scope_denied, token_exchange_failed or identity_failed; the full list is in the Accounts reference.

Your own OAuth apps

By default users sign in through Workwae's Google and Microsoft apps. Register your own to show your name on the consent screen and own the grant.

Register the callback with the provider

Add the redirect URI for your provider to the app at Google or Microsoft:

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

Register the app with Workwae

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": "common"
}'
  • Microsoft apps need a cloud (400 cloud_required): each cloud needs its own app registration. authority is the directory (tenant) id, or common.
  • Google apps take neither cloud nor authority (400 not_microsoft).

Use it

Pass its id as oauthAppIds on a hosted session, or as oauthAppId for headless OAuth and import.

  • PATCH /v1/oauth-apps/{appId} changes the name, client secret or authority. Accounts on the app use a new secret from their next token refresh.
  • An app cannot be deleted while accounts still use it.

Gmail push

Accounts on your own Google app can receive Gmail changes by push instead of polling.

  1. In the app's Cloud project, create a Pub/Sub topic and an authenticated push subscription to the app's gmailPushEndpoint, which is also the token audience. Settings > OAuth apps in the console shows a ready gcloud script.
  2. Set gmailPush (topic, serviceAccount) with PATCH /v1/oauth-apps/{appId}.

Setting it wakes the app's accounts so it applies at once; saving it again retries a watch Gmail refused. gmailPush: null stops the watches and returns the accounts to polling.

Microsoft clouds

microsoftCloudCloud
globalMicrosoft 365, including GCC (moderate). The default.
gcc-highUS Government GCC High.
dodUS Government DoD.
chinaMicrosoft 365 operated by 21Vianet.

microsoftCloud applies to sign-ins with Workwae's app, for the clouds Workwae runs one in; for a cloud without one, headless OAuth and import answer 400 provider_not_configured. For other clouds, register your own app per cloud; the app carries its cloud. The account reports it as cloud.

Reconnect an account

When a provider revokes a grant (a password change, an administrator removing consent), the account moves to reauth_required and an account.reauth_required event names the reason. Send the user through a connect flow again with the account's id:

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",
  "accountId": "6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b"
}'
  • The session signs in to the account's own provider, cloud, OAuth app and mail access; providers must include that provider. A password IMAP account gets the IMAP form.
  • Headless OAuth takes accountId too, with the account's address as the sign-in hint.
  • On success the same account becomes active again and account.connected is sent with reauthorized: true.
  • Accounts from an organization connection or a shared mailbox are restored through the connection or the delegating account instead.

Paused is yours, reauth_required is Workwae's

You can set an account to paused or active with PATCH /v1/accounts/{accountId}. reauth_required and disabled are set by the platform; reconnecting the account clears reauth_required.

On this page