OAuth Tokens Will Expire: Stop Silent 401 Loops in Production
n8n refreshes OAuth reactively on tokenExpiredStatusCode (default 401). Pause on auth failure, prefer service accounts, and stop quiet overnight 401 loops.
Your automation started failing with 401 / expired token because the access token died and n8n never got a refresh signal it understands — or the refresh token itself is gone — and nothing paused the workflow, so it looped quietly until a human noticed the CRM went dark.
n8n’s generic OAuth2 path refreshes reactively when the HTTP status matches tokenExpiredStatusCode (default 401). It does not proactively refresh from expires_in on every vendor. Webhook HMAC and signature verification are a different control — see Webhook security. This post owns credential lifecycle. Spine: Production n8n handbook.
The short answer
- Reactive refresh — on matching status (default 401), n8n refreshes and retries. Wrong status or body-only errors → no refresh.
- Reconnect “fixes” it for a day — you minted a fresh access token; the underlying refresh/consent problem remains.
- Production prefers service accounts / workspace apps over a founder’s personal Google login.
- Auth failures are pause-class incidents — stop the storm, fix the credential, then resume.
- Credentials live in n8n Credentials (or a secret manager) — never in sticky notes on the canvas.
How n8n refreshes OAuth in practice
| Step | Behavior |
|---|---|
| Request with access token | HTTP Request / node calls the API |
Response status == tokenExpiredStatusCode | Trigger refresh (or client-credentials fetch) |
| Refresh succeeds | Persist new oauthTokenData; retry the request |
| Refresh fails / wrong status | Error surfaces; no magic reconnect |
Default expired status is 401. Some APIs return 403 on expiry — set tokenExpiredStatusCode accordingly on the generic OAuth2 credential (field added in recent n8n releases; see credential UI / OAuth2Api credentials).
Known gap: vendors that return HTTP 200 with an error body when the token is dead never trip reactive refresh. That is working-as-designed today for generic OAuth2 (n8n issue #32423). You must detect those body codes yourself or reconnect on a schedule — do not assume expires_in alone saves you.
Why some APIs never trigger refresh
Decision list when “auto refresh” fails:
- Does the API return 401 (or your configured code) on expired access token?
- Did the provider issue a refresh token? Client Credentials often does not — by design you re-fetch, not refresh.
- Was the refresh token single-use / rotated and an older worker overwrote storage?
- Did scopes change so refresh is rejected until re-consent?
- Is the app in testing mode with tokens that expire in days (common on Google Cloud OAuth apps still in Testing)?
If (1) is no, fix detection or vendor choice before you blame n8n.
Personal OAuth vs service accounts
| Pattern | Use when | Failure mode |
|---|---|---|
| Personal Google / Microsoft OAuth | Prototypes, personal productivity | Offboarding, password reset, 2FA change, “Testing” token expiry |
| Workspace / company OAuth app + shared mailbox | Team ops with consent policy | Still tied to human approval if mis-scoped |
| Service account / server-to-server | Production backends that support it | Key rotation discipline required |
| Vendor API key / PAT | When OAuth is optional | Key leak blast radius — rotate on schedule |
Production rule: if the workflow outlives the employee’s laptop, it cannot depend on that employee’s interactive OAuth.
Failure mode: reconnect looks fixed for a day
What breaks: overnight 401s. Operator reconnects the credential. Morning looks green. Tomorrow (or next week) the same loop returns.
What actually happened: reconnect minted a new access token (and maybe a short-lived refresh). The root cause — Testing app, revoked refresh, wrong tokenExpiredStatusCode, personal account policy — is untouched.
What it costs: silent data gaps, duplicate “fixes,” and trust erosion when the board asks why the pipeline “keeps dying.”
What you do instead: treat reconnect as incident mitigation, then run the root-cause checklist below before you call it closed.
Incident response when credentials die overnight
Runbook (paste into your ops doc):
- Confirm — error is 401/403/auth, not schema or 429.
- Pause affected production workflows (and siblings sharing the credential).
- Alert once with credential name, workflows, first/last failure time — not a page per execution.
- Diagnose — refresh token present? status code mismatch? personal account? Google Testing mode?
- Rotate / reconnect using the correct account type; verify with a single staging or pinned-data run.
- Resume workflows deliberately; watch the next scheduled/webhook cycle.
- Write the postmortem line — root cause + permanent fix (service account, status code, monitoring).
Auth storms without pause are how you burn rate limits and fill DLQ with the same poison.
tokenExpiredStatusCode — what to set
On generic OAuth2 credentials:
Token Expired Status Code: 401 # default
# set to 403 if your API uses 403 for expired access tokens
Checklist:
- Document the vendor’s real expiry status from a captured response
- Set the credential field to match
- Prove refresh in staging by forcing expiry (short-lived token or revoked access)
- If vendor returns 200 + body error, add an explicit IF/Code branch — do not wait for n8n to guess
Rotate secrets without downtime
| Approach | Steps | Notes |
|---|---|---|
| Dual credential cutover | Create new credential → point staging → flip production nodes → revoke old | Best for API keys / second OAuth app |
| In-place reconnect | Pause → reconnect → single test → resume | Fine for true OAuth refresh repair |
| Env / credential overwrite | Inject via supported overwrite mechanisms | Keep encryption key/backup process intact |
Never paste client secrets into Slack. Never leave the old consumer key active “just in case” without a revoke date.
Offboarding and personal Google accounts
How companies break:
- Intern connects Gmail/Sheets with their user OAuth.
- Intern leaves; refresh is revoked.
- Nobody owns the credential; workflows stay active.
- Failures look like “n8n is flaky” for a week.
Controls:
- Credential owner field in your runbook (human name + backup)
- Ban personal accounts on SEV1 workflows
- Offboarding checklist includes n8n credential audit
- Alert on rising 401 rate per credential, not only per workflow
Where credentials should live
| Place | Allowed? |
|---|---|
| n8n Credentials store | Yes — default |
| Secret manager → injected at deploy | Yes — for self-hosted discipline |
| Canvas sticky notes / Set node hardcodes | No |
| Shared Google Doc “for the team” | No |
| Git repo | No (unless encrypted vault pattern you already operate) |
If someone needs a value to debug, grant time-boxed access to the credential UI — do not copy secrets into the graph.
Monitoring that catches quiet expiry
Minimum signals:
- Error workflow classifies
authfailures. - Threshold alert: N auth failures in M minutes → page + auto-pause candidate list.
- Weekly credential inventory: owner, type (personal vs service), last successful refresh/use.
- Staging job that exercises each critical credential on a schedule.
Quiet is the enemy. Green checkmarks on an old execution do not prove tomorrow’s refresh works.
Google “Testing” mode and short-lived grants
If your OAuth client is still in Testing in the provider console (common on Google Cloud), refresh tokens can expire on a short calendar (often days, depending on current Google policy for test users) even when n8n’s reactive refresh is configured correctly. Production automations need a published / production-grade app consent posture, or a non-user grant (service account) where the API allows it.
Checklist when Google-connected workflows die weekly:
- OAuth client publishing status is Production (or equivalent), not Testing
- Test users list is not the only path to a token
- Scopes match what production nodes actually call
- Credential owner is a company account, not a contractor personal Gmail
Reconnect without leaving Testing is the “fixed for a day” pattern with a calendar.
Credential inventory template
Copy into your runbook:
| Field | Example |
|---|---|
| Credential name in n8n | prod-google-sheets-ops |
| Vendor / app | Google Sheets — company Cloud project |
| Grant type | OAuth (workspace) / service account / PAT |
| Owner + backup | Jamie / Alex |
| Workflows using it | invoice-sync, lead-enrichment |
| SEV if dead | SEV1 / SEV2 |
tokenExpiredStatusCode | 401 |
| Last proven refresh | 2026-04-20 staging job |
| Offboarding risk | Personal? yes/no |
Review monthly. Orphans are incidents waiting for a Friday.
Pair auth failures with the error workflow
Auth is a first-class error class next to schema and rate-limit:
- Error Trigger / workflow-level error handler catches the failure.
- Classify
errorClass=auth. - Write DLQ row with credential name (not the secret).
- Notify with deep link + “paused?” recommendation.
- Optional: auto-disable a tagged set of workflows after N auth failures.
Do not page for every enrichment 401 if you already paused the critical path — mute siblings intentionally.
FAQ
Why does reconnecting “fix” it for a day?
Reconnect issues a fresh access token (and often a new refresh grant), so the next hours succeed. If the app is in Testing mode, the refresh policy is wrong, or the account will be revoked again, the same outage returns. Fix the root cause after the reconnect.
What is tokenExpiredStatusCode about?
It is the HTTP status n8n treats as “access token expired — refresh and retry” on generic OAuth2 credentials. Default is 401. Set it to 403 (or another code) when that is what your API returns on expiry; otherwise refresh never runs.
How do I rotate secrets without downtime?
Prefer dual credentials: create the new credential, validate in staging, flip production node mappings, then revoke the old secret. For OAuth reconnects, pause the workflows, reconnect, smoke-test once, then resume.
Should I pause workflows on auth failures?
Yes for production paths that would otherwise spam 401s. Pause (or disable) siblings sharing the dead credential, fix once, then resume. Leaving them active turns a credential incident into a rate-limit and alert-fatigue incident.
How do offboarding and personal Google accounts break companies?
Personal OAuth dies when the human leaves, resets a password, or loses consent. Production workflows should use service accounts or company-owned apps with a named owner and an offboarding audit that includes n8n credentials.
Where should credentials live — canvas notes?
Never on the canvas. Use the n8n Credentials store or a secret manager injection path. Sticky notes and Set-node secrets become leaks and unrotatable debt.
CTA
Treat OAuth like a production dependency with an owner — reconnect is mitigation, not a strategy.
Keep the handbook open for the rest of the spine. For a credential lifecycle review on your n8n estate, use automation or book a call.