How do agencies onboard multiple clients for Google OAuth on a self-hosted n8n instance
Onboard each client with their GCP project, n8n's exact OAuth callback, one Google credential, and a scripted walkthrough of the unverified-app warning.
William Spurlock Founder — Spurlock Studios 31 MIN
Agencies onboard multiple clients for Google OAuth on a self-hosted n8n instance by standing up a Google Cloud project, configuring the OAuth consent screen, pasting the exact redirect URL n8n shows (/rest/oauth2-credential/callback on your public origin), then creating one n8n Google credential per client and walking that person through consent — including Google’s unverified-app warning when the app is still in Testing or still requesting unverified sensitive scopes. Self-hosted n8n does not get n8n Cloud’s Managed OAuth2. Custom OAuth2 or it does not connect.
This is the Google Cloud OAuth client and the consent click. Folders, Projects, instance-per-client, and the license question of hosting client tokens on your box live on multi-client n8n isolation. Do not flatten those pages. The production spine — pause on auth drift, do not retry-storm a 401 — is the Production n8n handbook. If a Gmail send keeps dying on expired consent, park the payload on a dead-letter queue instead of reconnecting in a loop.
I have shipped 500+ automations. The agency graphs that survive onboarding are boring: a named GCP project, a redirect URI that matches the credential modal character-for-character, and a client who already saw a screenshot of “Google hasn’t verified this app” before they sat down.
A shared “agency Google” is not an onboarding shortcut. It is a mixed mailbox with extra steps.
The short answer
- Custom OAuth2, every time. n8n Cloud can Sign in with Google with no Cloud Console work on listed nodes. Self-hosted cannot. You build the app in Google Cloud.
- One n8n Google credential per client. One Cloud OAuth client ID per client is the honest default. Sharing one Client ID across ten mailboxes is how a revoke takes the floor with it.
- Redirect URL is copy-paste, not invented. n8n prints
OAuth Redirect URLon the credential. That string goes into Google’s Authorized redirect URIs. Behind a proxy, setN8N_WEBHOOK_URLso the printed string is nothttp://localhost:5678/.... - Consent screen is a product. Internal for one Workspace. External defaults to Testing: test users, seven-day grants, a warning. Brief it.
- Unverified is expected until it is not. Google hasn’t verified this app is the tester path, not a bug in n8n. Add the consenting account as a test user. Do not skip the briefing.
| Piece | Owner | Failure if you skip it |
|---|---|---|
| GCP project | Client, unless they have no Cloud org and you documented why | Agency-branded consent on a client mailbox |
| OAuth consent screen | Same project | Access denied, or a warning nobody explained |
| OAuth client (Web application) | Same project | invalid_client |
| Redirect URI | Copied from n8n | redirect_uri_mismatch |
| n8n credential | One per client, named | Shared Gmail, mixed tokens |
| Consent click | The client’s Google user | Your intern authorized their personal Drive |
Green Sign in with Google on your Gmail is homework. A client admin who consented their Workspace user into their named credential is onboarding.
What does agency Google OAuth onboarding actually mean?
It means you take a client from “we use Gmail and Sheets” to a named n8n credential that can call Google APIs as that client’s user or Workspace, with tokens stored in that n8n credential record. It is not “paste one Client ID into every Gmail node.” It is not “the intern logs in once and we reuse it.”
n8n’s own Google docs split the work into five steps: Cloud project, enable APIs, consent screen, OAuth client, then finish the n8n credential. That sequence is identical for single-service (Gmail node, Sheets node) and generic Google OAuth2. Generic adds scopes you type by hand. Single-service does not.
| Job | Honest meaning | Counterfeit |
|---|---|---|
| Onboard | Client Google user consents into a credential named for them | Agency founder consents “for now” |
| Isolate Google apps | Separate Cloud project / OAuth client per client | One Client ID, many Sign-ins |
| Isolate n8n tokens | Separate n8n credential records | One credential, swap which mailbox later |
| Isolate tenancy | Separate instance or licensed Projects — other post | Folders named acme/ |
| Done | Client can revoke without killing sibling clients | “We’ll rotate later” |
Decision list — if you cannot check these, you have not onboarded:
- Can you point at the GCP project id in writing?
- Does the n8n credential name include the client, not
Google account? - Did a person at the client complete Sign in with Google, not you?
- Is the redirect URI in Google identical to the credential modal, including
httpsand the path? - Did that person see (or skip, because Internal) the unverified warning on purpose?
Onboarding is a consent event. A JSON credential export you copied from staging is not a consent event.
Why is Custom OAuth2 mandatory on self-hosted n8n?
Because Managed OAuth2 is an n8n Cloud product. The Google credentials hub lists Calendar, Chat, Contacts, Docs, Drive, Gmail, Sheets, Slides, Tasks, and YouTube as Managed on Cloud. Self-hosted is told to use Custom OAuth2: you create the app in Google Cloud Console and paste Client ID and Client Secret into n8n.
That is the whole hosting split for Google. Do not open a Cloud Console “because we are serious” on n8n Cloud if Managed covers the node. Do not look for a Sign in with Google shortcut on self-hosted that skips Console. It is not there.
| Host | Google OAuth you actually get | Console work |
|---|---|---|
| n8n Cloud, listed nodes, Managed | n8n’s app; Sign in with Google | None required |
| n8n Cloud, you still want your own app | Custom OAuth2 | Full five steps |
| Self-hosted, any Google node | Custom OAuth2 only | Full five steps |
| Self-hosted, generic OAuth2 API | Custom + scopes you type | Same, plus scope list |
Procedure if someone asks “can we just use the Cloud button on our VPS?”:
- Open the credential. If the only path is Client ID / Client Secret / Sign in with Google, you are on Custom.
- Confirm
WEBHOOK_URL/N8N_WEBHOOK_URLis the public origin, not an unset default. - Create or open the GCP project.
- Stop looking for Managed. It will not appear.
Service accounts are a different credential type. n8n supports them on some Google nodes (Sheets, Drive, BigQuery, Docs). n8n is explicit that Gmail via service account wants domain-wide delegation, which Google discourages, and behavior can be inconsistent. n8n recommends OAuth2 for Gmail. Agency onboarding for mail is OAuth, not a JSON key you dropped in a shared Drive.
Custom OAuth2 is the self-hosted door. Managed is a Cloud convenience. Mixing those sentences in a sales deck is how a client thinks Google will “just work” on your Docker box.
Why one Google credential per client, not one agency login?
Because the n8n credential is where the refresh token lives. One Sign in with Google mints a grant for one Google user into one credential. Reusing that credential on Client B’s workflows means Client B’s Sheets node is Client A’s Google user. That is not multi-client. That is a confused deputy.
Worse pattern: one Google Cloud OAuth client ID, many n8n credentials. Each Sign-in still hits the same app. Consent branding is yours. The unverified-app user cap, if it applies, is on that project. A Workspace admin who blocks “unverified apps” blocks every client at once. A revoke of the Cloud client secret kills every n8n credential that stored it.
| Pattern | What Google sees | What n8n stores | Honest for agencies? |
|---|---|---|---|
| One agency Gmail, one n8n credential, every client workflow | One user | One grant | Never for production mail or Drive |
| One Cloud OAuth client, many n8n credentials | One app, many users | Many grants, one Client ID/secret | Better than shared Gmail; still one blast radius on secret/cap/branding |
| Per-client Cloud OAuth client, per-client n8n credential | Separate apps | Isolated grants and secrets | The default |
| Per-client Cloud project plus the row above | Separate consent screens, test lists, verification | Isolated grants | Best when clients will own the project |
Checklist before you call a Google node “Client A’s”:
- Credential name is
acme-gmail-oauth(or similar), notGoogle - Client ID in n8n matches the OAuth client in Acme’s Cloud project (or a project you labeled Acme)
- The Google user who consented is an Acme mailbox, not
you@agency - Client B’s workflows cannot select Acme’s credential unless an admin intended sharing
- Offboard = delete that n8n credential and revoke the Cloud client or the user’s grant — not “we’ll rename the credential”
n8n can share a credential so other users use it without seeing the secret, on plans that include sharing. That is a collaboration feature. It does not make one Gmail grant safe for two companies.
If your instance is already a mixed kitchen, stop onboarding new Google clients onto the shared Client ID. New clients get a new Cloud client. Old ones get a migration window. Do not “fix” ten years of shared Gmail in the same afternoon you add Client Eleven.
Who should own the Google Cloud project?
The client, whenever they have a Google Cloud org or a Workspace that can own one. Then the consent screen can be Internal (org users only, no unverified screen, no 100-user cap on that Internal path). You still help click the buttons. You do not become the app.
Agency-owned projects are for clients who will not open Cloud Console and have signed something that says you operate an OAuth app on their behalf. Even then: one project per client, not a studio mega-project named n8n-prod.
Google’s own when verification is not needed page is the decision table. Internal apps used only by people in the same Workspace / Cloud Identity org, with the project owned by that org, skip the unverified screen and the user cap. Personal-use and Testing apps can keep going with click-throughs and caps. A public product that wants many users and sensitive scopes verifies.
| Client situation | Project owner | User type | Why |
|---|---|---|---|
| Workspace, only org users will consent | Client org | Internal | Google’s Internal path is built for this |
| Mixed consumer Gmail + Workspace | Client project | External | Internal cannot consent @gmail.com |
| Agency sandbox, no client Cloud access yet | Agency, labeled with client name | External + Testing | Temporary; calendar-dead grants in seven days |
| You want one studio app “for all clients” | Do not | — | Shared branding, shared cap, shared secret |
Procedure to pick ownership in the kickoff:
- Does the client have Workspace? If yes, prefer their org owns the project.
- Will any consenting user sit outside that org? If yes, Internal is a lie — use External.
- Can a client admin create a Cloud project this week? If no, you create one named for them, then transfer when they can.
- Write the project id in the SOW. “We’ll use ours” without an id is how you lose the project six months later.
Do not invent a Google “projects per org” number here. Google’s quotas move. If a create fails, read the error in Cloud Console. Do not quote a forum cap.
Transfer of a project is a Cloud IAM event. If you skip it, you still own the OAuth client when the retainer ends. That is an offboarding bug, not a feature.
How do you configure the OAuth consent screen?
You configure it before you mint the OAuth client, in the same GCP project, following n8n’s consent-screen steps. Google now routes this through Google Auth Platform. App name and support email are what the human reads. Audience is Internal vs External. Authorized domains must include the domain of your n8n instance (self-hosted) — n8n Cloud users add n8n.cloud instead.
External defaults to Testing. n8n’s docs are blunt: only Google accounts you add as test users can complete the flow. Everyone else gets access denied. That is not n8n being picky. That is Google.
| Consent field | What to put | Agency trap |
|---|---|---|
| App name | The client’s product or “Acme n8n”, not your studio nickname | Client sees “Spurlock sandbox” on their Gmail |
| User support email | An inbox a human reads | A no-reply you do not watch |
| Audience / user type | Internal if truly org-only; else External | Internal, then a contractor on @gmail.com |
| Authorized domains | Hostname of self-hosted n8n | localhost as a “domain”; leftover ngrok-free.app |
| Test users (External + Testing) | Every Google account that will click Sign in | You tested with yours; the client is not on the list |
| Scopes on the screen | The scopes the n8n node will request | Console lists three; n8n requests a restricted Gmail scope |
Numbered setup (n8n’s sequence, agency notes in the last column of your runbook):
- Google Cloud Console → correct project selected in the top dropdown.
- APIs & Services → OAuth consent screen → Get started.
- App name, support email, Next.
- Audience: Internal or External. Next.
- Contact email Google can use about the project. Accept the User Data Policy. Create.
- Branding → Authorized domains → add the n8n host domain → Save.
- If External: Audience → Test users → add the client’s consenting account before they sit down.
Scopes you request in the n8n credential must match what you declared. Google’s unverified apps help says a mismatch between requested scopes and the consent-screen configuration is enough to show the unverified screen. Least privilege is not a slogan. Extra Gmail scopes are how a Sheets-only job inherits a restricted-scope warning.
If you ever submit verification, Google’s unverified apps page wants a privacy policy URL, site ownership in Search Console, and justification for sensitive or restricted scopes. That is a product launch, not an onboarding meeting. Most agency retainers never need it if User type is Internal. Do not start a verification ticket to “look professional” on a three-user Workspace.
Publish-to-production superstition: publishing without verification, while requesting sensitive or restricted scopes, still shows an unverified warning. Google’s audience page says that out loud. Testing vs In production is not “the warning goes away.” Verification is.
Consent-screen checklist before you create the OAuth client:
- Correct project in the Cloud top dropdown
- App name a client would recognize on their phone
- User type matches who will actually click
- Authorized domain is the n8n host, not a leftover tunnel
- Test users added if External + Testing
- Declared scopes will match the n8n credential
- No verification ticket opened “just in case”
What redirect URL does self-hosted n8n send?
n8n shows an OAuth Redirect URL on the Google credential. You copy that exact string into the Cloud OAuth client’s Authorized redirect URIs. n8n documents the local shape as http://localhost:5678/rest/oauth2-credential/callback. Google allows localhost for development. A public production box must present a public URI, usually https://<your-n8n-host>/rest/oauth2-credential/callback.
n8n composes public URLs from N8N_PROTOCOL, N8N_HOST, and N8N_PORT. Behind a reverse proxy that is wrong, because n8n still thinks it is on 5678. Set N8N_WEBHOOK_URL to the HTTPS origin (https://n8n.example.com/) and N8N_PROXY_HOPS=1. As of n8n 2.35.0, WEBHOOK_URL is a deprecated alias of N8N_WEBHOOK_URL and logs a warning. N8N_EDITOR_BASE_URL is the public editor URL (emails, SAML). Do not assume it is the OAuth callback. The credential modal is the source of truth.
If you changed env after the credential already existed, delete and recreate the Google credential. Operators routinely report the callback baked at create-time. Pasting a new URI in Google while n8n still sends the old one is redirect_uri_mismatch.
| What you see in n8n | What it means | Fix |
|---|---|---|
http://localhost:5678/rest/oauth2-credential/callback on a VPS | Public URL not configured | Set N8N_WEBHOOK_URL, restart, recreate credential |
https://n8n.example.com/rest/oauth2-credential/callback | Healthy default REST path | Paste exactly into Google |
Path is not /rest/... | N8N_ENDPOINT_REST is not rest | Register the path n8n actually prints |
Google error redirect_uri_mismatch | String mismatch: scheme, host, port, slash, path | Copy from n8n again; do not retype |
| Callback 401 after Google already approved | Session / auth-on-callback mismatch | See n8n 2.0 N8N_SKIP_AUTH_ON_OAUTH_CALLBACK; confirm for your version |
n8n 2.0 breaking changes flipped N8N_SKIP_AUTH_ON_OAUTH_CALLBACK from default true to default false (callback requires an authenticated n8n session). Google’s redirect lands in the browser that started Sign in. If that browser still has the n8n session, you are fine. If the callback host differs from the editor host, cookies die and you get a generic Unauthorized. Confirm against the changelog for the version you run. Do not cargo-cult =true onto a box that already completes consent.
Redirect URI checklist:
- Open a new Google credential so you see the current URL
- Copy, do not retype
- Google OAuth client → Web application → Authorized redirect URIs → paste
- Scheme matches (
httponly for localhost; production ishttps) - No trailing path drift (
/callbackvs/callback/) - Test the URI in a browser; n8n should complain about missing OAuth params, not Nginx 404
- Recreate the n8n credential after any
N8N_WEBHOOK_URLchange
Google’s web-server OAuth docs treat localhost as a special development case. A public hostname is expected to be HTTPS. If Console refuses http://n8n.example.com/..., that is Google, not n8n. Put TLS on the box, set N8N_WEBHOOK_URL=https://.../, recreate the credential, paste the new URI.
Smoke-test the callback before the client meeting:
- Copy the URI from n8n.
- Open it in a logged-out browser window.
- You want an n8n OAuth error about missing parameters, not a proxy 404, not a Cloudflare login, not the editor homepage.
- Only then paste it into Google.
A localhost redirect on a production hostname is the first ticket I see when an agency “finished OAuth” on a laptop and then pointed DNS at Docker.
How do you enable APIs and create the OAuth client?
Enable only the APIs the nodes need, in the same project as the consent screen. n8n’s enable-API list is the map: Gmail API for the Gmail node; Sheets API and Drive API for Sheets; Docs and Slides also need Drive. Ads needs a developer token. Vertex needs Vertex plus Cloud Resource Manager. Do not enable the kitchen sink “so we don’t come back.” Extra APIs are how someone later adds a Gmail node to a Sheets-only app and surprises the consent screen.
Then create OAuth client ID, type Web application, name it so you can find it at 2am (acme-n8n-web). Paste n8n’s redirect URI. Create. Copy Client ID and Client Secret once into the n8n credential. The secret is not Slack-safe.
| Node you will attach | APIs to enable (n8n docs) | Credential flavor |
|---|---|---|
| Gmail / Gmail Trigger | Gmail API | OAuth2 single-service |
| Google Sheets / Trigger | Sheets API and Drive API | OAuth2 single-service |
| Google Docs / Slides | Docs or Slides and Drive API | OAuth2 single-service |
| Drive / Drive Trigger | Drive API | OAuth2 single-service |
| Calendar | Calendar API | OAuth2 single-service |
| HTTP Request to a Google API | The API you call | Generic Google OAuth2 + explicit scopes |
| BigQuery / some Drive jobs | Matching API | OAuth2 or Service Account where n8n allows |
Numbered create path:
- Console → correct project.
- APIs & Services → Library → search → Enable.
- Credentials → Create credentials → OAuth client ID.
- Application type: Web application.
- Name it for the client.
- Authorized redirect URIs ← n8n OAuth Redirect URL.
- Create. Copy ID and secret into n8n. Store the secret in the client’s vault, not in Notion.
invalid_client almost always means the ID or secret in n8n does not match Console — whitespace on paste, old secret after a rotate, or the wrong project’s client. n8n’s troubleshooting says copy both values fresh.
Google Ads, Perspective, and a few others have extra access gates. If the Library page says request access, that is a Google process. n8n cannot skip it.
Do not mint a Desktop or iOS client “because the dropdown was there.” n8n’s Google docs say Web application. Wrong client type is a redirect and secret mismatch you will not debug by restarting Docker.
How do you create and connect the n8n credential?
Create the credential in n8n first far enough to see the redirect URL, configure Google, then finish n8n: paste Client ID and secret, set scopes if generic, then Sign in with Google. The client’s Google user must complete that click. Then Save.
Single-service credentials do not ask you for a scope string. Generic Google OAuth2 does. n8n does not support every Google scope; unsupported scopes fail. Use the supported scopes table on the generic page. Space-separated, not commas.
| Step | Where | Who |
|---|---|---|
| New credential, copy OAuth Redirect URL | n8n | You |
| Paste URI, create Web client | Cloud Console | You or client admin |
| Paste Client ID + secret | n8n | You |
| Generic: enter scopes | n8n | You, least privilege |
| Sign in with Google | Browser | Client Google user |
| Save | n8n | You, after green |
Agency operating rules for the Sign-in:
- Use a meet where the client shares their screen, or a room they control. Do not take their password.
- They use the account that should own the mailbox / Drive. Not a personal Gmail “to test.”
- If External + Testing, that account is already a test user.
- If the unverified screen appears, they expected it. You already sent the screenshot.
- After redirect back to n8n, you confirm the credential saved and a Gmail or Sheets node executes one read-only probe.
- You do not send mail on the probe.
n8n Cloud users who picked Custom instead of Managed follow the same paste-ID-and-secret path. Self-hosted has no other path.
If Sign in opens, then dies on access_denied, the account is not a test user, the app is Internal and the account is outside the org, or a Workspace admin is blocking third-party apps. Fix Google, not the node.
Single-service vs generic, pick once:
| You are attaching | Use | Do not |
|---|---|---|
| Gmail, Sheets, Drive, Calendar, Docs, Slides, … official node | Single-service Google credential for that node | A mega generic with every Gmail scope “for later” |
| HTTP Request to a Google URL | Generic Google OAuth2, scopes from n8n’s supported list | Random scopes copied from a Stack Overflow dump |
| One client, both Gmail and Sheets | Two credentials, or two Cloud clients if you want separate grants | One credential with mail+Drive because it was convenient |
Least privilege on generic is how you keep a Sheets job off the restricted Gmail warning. A kitchen-sink scope string is how a read of one tab inherits a verification story.
Save is not optional. A completed consent that nobody clicked Save on is a grant Google holds and n8n does not. You will reconnect tomorrow and call it flaky.
What is the unverified-app warning, and who click-throughs it?
It is Google telling the user this OAuth client has not completed verification for the sensitive or restricted scopes it is asking for — or the app is still in Testing. n8n documents the same screen as Google hasn’t verified this app. Google’s Help Center calls these unverified apps.
For agencies, the warning is usually correct. You are not launching a consumer product. You are connecting one company’s n8n to that company’s Google data. Google still shows the interstitial.
n8n’s documented tester path:
- Internal: create credentials from the same org context; org users should not need the consumer unverified path.
- External: add the consenting email on the Audience page as a test user.
Google’s audience page: Testing is limited to up to 100 test users; a test user consumes quota when added. Google shows a warning before those testers authorize. Authorizations (and refresh tokens) for Testing + External expire after seven days. Exception: if you only request basic identity (openid / email / profile), testers, warnings, and the seven-day rule do not apply that way. Gmail and Drive scopes are not that exception.
Google also publishes a new user cap of 100 new users in total after an app presents the unverified-app screen, for apps requesting unapproved sensitive or restricted scopes. That cap is per project lifetime in Google’s table. If Cloud Console shows a different figure, believe Console — blog round-ups are not a source. Brief clients from Manage App Audience and Unverified apps.
| Situation | What the user sees | What you do |
|---|---|---|
| Internal, org user | Normal Workspace consent, in typical setups | Still confirm with a dry run |
| External + Testing, user on test list | Warning, then consent | Brief + screenshot before the meeting |
| External + Testing, user off list | Access denied | Add the user; do not “retry n8n” |
| In production, sensitive scopes, not verified | Unverified-app screen + user cap | Verify, or stay in the <100 personal-use story Google documents |
| Scopes in n8n ≠ scopes on the consent screen | Unverified screen even if you thought you were done | Align scopes |
Google’s interstitial copy moves. As of the current consumer flow, testers often have to open an Advanced (or similar) control and confirm they are going to the unverified app. I will not freeze button labels here. Show the client a screenshot from your dry run the same week, with the app name highlighted. If Google’s UI does not match the screenshot, stop and re-dry-run. Do not talk them through a 2022 blog’s clicks.
Who click-throughs: the client, on their account, after you told them the app name they should see. You do not send “ignore Google, it always says that.” You send: “You will see Google hasn’t verified this app. The app name should be Acme n8n. That is the Cloud project we created. If the name is wrong, stop.”
Workspace admins can mark apps trusted or block unverified third-party access for the domain. If Sign-in dies only for @client.com and works for a personal test user, you are in Admin console, not in n8n. Bring the Workspace admin to the second sitting. Do not keep creating OAuth clients until one sneaks through.
Verification is a Google process: privacy policy URL, Search Console ownership, justification for sensitive and restricted scopes. Google says some reviews can take a long time. Do not quote a week count I did not source. For most agency retainers with a handful of Workspace users, Internal or Testing-with-test-users is the honest shape — and you reconnect on the seven-day clock until you leave Testing.
Seven-day Testing expiry is the quiet killer. n8n’s own troubleshooting: reconnect in the credentials modal. Put it on a calendar. A “set and forget” Gmail node on Testing is a weekly outage.
What usually fails first?
redirect_uri_mismatch, then access denied (not a test user), then the seven-day Testing grant, then a callback that still says localhost, then the client bouncing off the unverified screen because nobody warned them.
The expensive failure is not a red modal. It is a Gmail node that worked on Friday, died Thursday, and retried into a 401 storm all night. Auth drift is pause-class. Put the payload on a dead-letter path. Do not reconnect seventeen times from an error workflow.
| Symptom | Likely cause | First fix |
|---|---|---|
redirect_uri_mismatch | URI in Google ≠ n8n | Copy from credential; check N8N_WEBHOOK_URL |
access_denied / app not verified | Test user missing, or Internal vs @gmail.com | Audience → Test users, or User type |
invalid_client | Bad ID/secret, wrong project | Re-copy from Console |
| “Google hasn’t verified this app” panic | Expected Testing/unverified path | Brief; confirm app name |
| Works 7 days, then 401 | External + Testing | Reconnect; plan Internal or verification |
| Callback Unauthorized after Google OK | Host/session/N8N_SKIP_AUTH_ON_OAUTH_CALLBACK | Same browser/host; read 2.0 changelog |
| Client B’s Sheets shows Client A’s tabs | Shared credential or shared Google user | Split credentials; rotate |
| Gmail via service account “should be easier” | Domain-wide delegation mess | OAuth2, per n8n |
Failure-mode drill before the first paid mailbox:
- Consent with a test user on a read node.
- Revoke the app in the Google Account’s third-party access page. Confirm n8n fails closed.
- Reconnect. Confirm one execution, not a retry burst.
- Wait — or simulate — Testing expiry if you are still on Testing. Confirm an alert, not silence.
- Confirm Client A’s revoke does not invalidate Client B.
If you cannot name who gets the Slack when Gmail 401s, you did not onboard. You hid a token in a credential named Google.
Workspace admins can block unverified apps for the whole domain. That looks like n8n “randomly” failing for one client. It is an admin policy. The fix is Internal + admin allow, or a verified app, or an exception — not a new node.
How do you onboard a client in one sitting?
You do not. You do prep, then a 45-minute consent meeting, then a probe. The Cloud project and APIs happen before the client joins. The meeting is Sign in with Google plus the warning. The probe is a read-only node on a published workflow with an error workflow attached.
| Phase | You ship | Client ships |
|---|---|---|
| Before the call | Project, APIs, consent screen, OAuth client, n8n credential shell, N8N_WEBHOOK_URL confirmed, screenshot of the warning | Test-user email, Workspace admin who can allow the app if blocked |
| On the call | Watch the app name; do not drive their password | Sign in with Google; click through only if the name matches |
| After the call | Save credential; read-only probe; name the credential; write project id in the runbook | Confirm they can revoke; calendar for seven-day reconnect if Testing |
Numbered sitting (once prep is done):
- Confirm in n8n the OAuth Redirect URL is the public HTTPS callback, not localhost.
- Confirm in Google the same URI is listed.
- Confirm the client’s account is a test user, or User type is Internal and they are in-org.
- Client: Sign in with Google.
- Walk the unverified interstitial if it appears. Stop if the app name is wrong.
- n8n: Save.
- Execute a Sheets get or Gmail get (not send) on a disposable id.
- Attach the shared error workflow. Prove a Stop And Error would page someone.
- Write: project id, OAuth client name, n8n credential name, Google user, User type, Testing or Internal, next reconnect date if Testing.
Prep checklist you finish without the client:
-
N8N_WEBHOOK_URLset, n8n restarted, new credential shows public callback - APIs enabled
- Consent screen saved; authorized domain = n8n host
- Web application OAuth client created
- Client ID/secret in n8n, not yet signed in
- Screenshot of unverified warning in the invite
- Probe workflow unpublished or pointing at a sandbox spreadsheet / label
If the client cannot join a screen-share, they complete Sign in on a call where they control the keyboard. You do not collect a refresh token file “to save time.”
One sitting that skips prep is a live debug of redirect_uri_mismatch in front of a paying customer. That is not onboarding. That is a demo of your unset env.
How do you measure that onboarding worked?
You measure grants that still work, clients that can revoke in isolation, and alerts on auth failure — not “number of Google nodes in the account.” A credential that is green in the UI and 401 in production is a failed onboard.
| Signal | Pass | Fail |
|---|---|---|
| Consent user | Client-owned Google user on the runbook | Agency inbox |
| Credential uniqueness | One n8n Google credential per client (per product if you split Gmail vs Sheets) | Google used everywhere |
| Redirect | Modal URI = Console URI | Localhost in production Console |
| Testing clock | Next reconnect dated, or not in Testing | “It’ll be fine” |
| Blast radius | Revoke A, B still reads | Both die |
| Auth alerting | One alert per credential, then pause | Retry storm into Gmail |
| Unverified briefing | Client expected the screen | They bounced and you called it a Google outage |
Weekly (or on the seven-day Testing cadence):
- Sample one read per live Google credential.
- Confirm the Google user still matches the runbook (people leave).
- Confirm nobody created a second “temporary” Google credential for a hotfix.
- If Testing, reconnect before the grant dies, or leave Testing for Internal / verified.
Do not invent a studio-wide “hours to first Gmail” metric. Time-to-onboard is your prep plus their admin availability. The only number that matters after go-live is: did this credential 401, and did a human get one alert?
If you cannot list every Google credential on the instance with a client name, you cannot measure isolation. Start there.
When should you hire vs DIY this?
DIY if you already run the self-hosted box, can set N8N_WEBHOOK_URL, and the clients are Workspace-internal with an admin who will own the Cloud project. Hire when you are about to put client Gmail on your instance, when External + restricted scopes are in play, or when you cannot explain Testing vs Internal in one paragraph.
| Situation | DIY | Hire / audit |
|---|---|---|
| Your company, your Workspace, Internal app, self-hosted n8n you already operate | Yes | Optional review of redirect + error workflow |
| Three clients, each with their own Cloud project, you only build graphs | Usually | If you also host their tokens, read the isolation/license spoke first |
| One agency Google, ten client mailboxes | No | Stop. Split credentials before you add a node |
| External, Gmail send, still in Testing | Only with a reconnect calendar | If you cannot staff the seven-day clock |
| Unverified app, growing past Google’s documented user cap | No | Verification or Internal; this is Google’s process |
You have never set N8N_WEBHOOK_URL and production is already live | Maybe, if you can take a maintenance window | If OAuth is already “working” on localhost URIs in Console |
Hire vs DIY is not “can I click Create credentials.” It is whether you will still be the person who reconnects when Testing dies on a Thursday, and whether hosting those refresh tokens on your database is a model you have actually checked with n8n. That license question is the isolation post, not a paragraph I will fake here.
Skip DIY this week if: you cannot get a public HTTPS origin, the client will not join a consent call, or the only Google user you have is yours.
A $500 Automation Audit is for the instance that already has six “temporary” Google credentials and a localhost redirect in production Console. Bring the project ids.
FAQ
How do agencies onboard multiple clients for Google OAuth on a self-hosted n8n instance?
Custom OAuth2 per client: Google Cloud project, consent screen, Web application client, n8n’s exact /rest/oauth2-credential/callback URI, then one n8n Google credential whose Sign in with Google is completed by the client’s user. Self-hosted has no Managed Google button. Brief the unverified-app warning before the meeting. Tenancy (who hosts the box) is a separate design.
How do I measure whether Google OAuth onboarding on self-hosted n8n is working?
Count live credentials that still read as the client’s Google user, revokes that do not take sibling clients with them, and auth failures that alert once and pause instead of retrying. On External + Testing, also count reconnects before the seven-day grant dies. A green credential icon is not the metric. A Thursday Gmail get that still returns mail is.
What usually fails first when teams try this?
redirect_uri_mismatch from a localhost callback or a mistyped URI, then access denied because the client is not a test user. Next is the seven-day Testing expiry that looks like “n8n broke.” Then a shared agency Gmail used on two client graphs. Fix the printed redirect URL and the test-user list before you add nodes.
How long does this take to show results?
Prep (project, APIs, consent, OAuth client, public N8N_WEBHOOK_URL) is hours if the box already has a real HTTPS origin. The consent meeting is short. A truthful production signal is a read-only probe plus the first week without a surprise 401 — or the first scheduled reconnect if you are still on Testing. You will not get “OAuth is done” from a single green Sign-in on the founder’s laptop.
What should I skip if I only have a week?
Skip verification paperwork, skip a studio-wide mega Cloud project, skip Gmail send, skip Managed-OAuth folklore on self-hosted. Ship: public callback URL, one client project, test users, one n8n credential, a read-only probe, an error workflow, and a written Testing reconnect date. If N8N_WEBHOOK_URL still prints localhost, spend the week on that, not on a second client.
When is this not worth doing yet?
When you do not have a public HTTPS n8n origin, when the client will not complete Sign in with Google as themselves, or when the plan is one agency Gmail for every customer. Also wait if you cannot name an owner for 401s. Get the box reachable and the isolation story honest first. OAuth on a mixed kitchen just mints mixed tokens faster.
CTA
Google OAuth on self-hosted n8n is a Cloud project, an exact callback, and one credential per client — not a shared inbox.
Read the Production n8n handbook, skim the automation lane, and book a $500 Automation Audit if you want the redirect, the consent screen, and the credential split checked on your instance.
What questions does this article answer?
- How do agencies onboard multiple clients for Google OAuth on a self-hosted n8n instance?
- Custom OAuth2 per client: Google Cloud project, consent screen, Web application client, n8n's exact `/rest/oauth2-credential/callback` URI, then one n8n Google credential whose Sign in with Google is completed by the client's user. Self-hosted has no Managed Google button. Brief the unverified-app warning before the meeting. Tenancy (who hosts the box) is a separate design.
- How do I measure whether Google OAuth onboarding on self-hosted n8n is working?
- Count live credentials that still read as the **client's** Google user, revokes that do not take sibling clients with them, and auth failures that alert once and pause instead of retrying. On External + Testing, also count reconnects before the seven-day grant dies. A green credential icon is not the metric. A Thursday Gmail get that still returns mail is.
- What usually fails first when teams try this?
- `redirect_uri_mismatch` from a localhost callback or a mistyped URI, then access denied because the client is not a test user. Next is the seven-day Testing expiry that looks like "n8n broke." Then a shared agency Gmail used on two client graphs. Fix the printed redirect URL and the test-user list before you add nodes.
- How long does this take to show results?
- Prep (project, APIs, consent, OAuth client, public `N8N_WEBHOOK_URL`) is hours if the box already has a real HTTPS origin. The consent meeting is short. A truthful production signal is a read-only probe plus the first week without a surprise 401 — or the first scheduled reconnect if you are still on Testing. You will not get "OAuth is done" from a single green Sign-in on the founder's laptop.
- What should I skip if I only have a week?
- Skip verification paperwork, skip a studio-wide mega Cloud project, skip Gmail send, skip Managed-OAuth folklore on self-hosted. Ship: public callback URL, one client project, test users, one n8n credential, a read-only probe, an error workflow, and a written Testing reconnect date. If `N8N_WEBHOOK_URL` still prints localhost, spend the week on that, not on a second client.
- When is this not worth doing yet?
- When you do not have a public HTTPS n8n origin, when the client will not complete Sign in with Google as themselves, or when the plan is one agency Gmail for every customer. Also wait if you cannot name an owner for 401s. Get the box reachable and the isolation story honest first. OAuth on a mixed kitchen just mints mixed tokens faster.
Last reviewed
Automation
Automation After the show is not you at 1 a.m.
Post-show onboarding — thank-you, join path, merch nudge — belongs in a human-gated n8n rail, not your thumb at load-out.
Automation Paperwork that is not the plant
Invoice and PO matching, intake, and support triage in n8n with Metrc fences — the paperwork operators hate, not a menu widget.
Automation Saturday still books — the missed-call rail for trades
A missed-call text-back that routes zip and books a slot beats voicemail and Saturday desk coverage you cannot keep staffed. If a kid is cheaper, say so.
Automation Why doesn’t worker concurrency cap my n8n sub-workflows
Worker concurrency does not cap n8n sub-workflows. Each Execute Workflow child is a new execution the production limit skips, usually on the parent worker.
Will's Journal in your inbox.
What I learned this week building for shops, floors, and houses.
You're on the list.
Sign-up failed — try again.
By subscribing, you agree to the Privacy Policy.