OAuth Tokens Will Expire: Stop Silent 401 Loops in Production
n8n refreshes OAuth on 401, not expires_in. Persist refresh tokens, set tokenExpiredStatusCode, pause on auth, and alert before quiet overnight 401 loops.
William Spurlock Founder — Spurlock Studios Updated 20 MIN
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. Overnight auth is a severity problem — see When automation fails overnight. This post owns credential lifecycle: refresh tokens, expiry alerts, and the pause that stops the 401 storm. 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.
- Refresh tokens outlive access tokens — persist the rotated refresh token. Client Credentials often has no refresh token; you re-fetch.
- 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 — one expiry alert per credential, stop the storm, fix the grant, then resume.
How does n8n refresh OAuth in practice?
n8n waits for a failed request, then refreshes if the status matches. Staff documented the same behavior years ago: the HTTP Request node refreshes when a request returns that the token is no longer valid, then retries (OAuth2 token refreshing). That is still the generic path.
| 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. The credential field exists on generic OAuth2 (tokenExpiredStatusCode, default 401) in OAuth2Api.credentials.ts. Some APIs return 403 on expiry — set the field to match.
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.
Decision list when “auto refresh” looks broken:
- Did the API return 401 (or your configured code) on the expired access token?
- Did the provider issue a refresh token at all?
- Did n8n persist the new refresh token after rotation?
- Is the app still in Google Testing, so the refresh grant itself is calendar-dead?
If (1) is no, fix detection before you blame n8n.
Why do some APIs never trigger a refresh?
Refresh never runs when the rail never sees an expired-token status. The graph looks green. The vendor body says the token is dead. Operators call that “n8n flakiness.” It is a detection miss.
| Vendor signal | n8n generic OAuth2 | What you do |
|---|---|---|
| HTTP 401 | Refresh + retry (default) | Prove it in staging with a short-lived token |
| HTTP 403 meaning expired | No refresh unless you set the field | Set tokenExpiredStatusCode to 403 |
HTTP 200 + body error (602, invalid_token) | No refresh (#32423) | IF/Code branch or Stop and Error on the body code |
HTTP 400 invalid_grant on the token endpoint | Refresh already failed | Pause. Re-consent. Do not retry the CRM write |
| No refresh token (client credentials) | Re-fetch access token, do not “refresh” | Confirm grant type; RFC 6749 says no refresh token here |
RFC 6749 defines the refresh token as a credential used to obtain new access tokens. Section 4.4.3 says a client-credentials access-token response should not include a refresh token. If you built a “refresh” step for that grant, you built a fiction.
Checklist when a vendor “never refreshes”:
- Capture the raw status and body from a dead-token call
- Map that status onto
tokenExpiredStatusCodeor an explicit branch - Confirm a refresh token exists in the stored
oauthTokenData - Confirm the token endpoint error is not
invalid_grant(refresh is already dead) - Do not store tokens in workflow static data as a workaround — that leaves the credential store
Quiet 200s are the expensive case. They never hit the error workflow unless you throw on purpose.
What is a refresh token actually doing?
An access token is the short-lived bearer you send to the API. A refresh token is the longer-lived grant you send to the token endpoint to mint a new access token without a human in the browser. Google’s own overview is blunt: access tokens have limited lifetimes; if you need access beyond that, you obtain a refresh token and use it (Using OAuth 2.0 to Access Google APIs).
| Artifact | Typical life | Stored where in n8n | Dies when |
|---|---|---|---|
| Access token | Minutes to hours | Inside oauthTokenData | Clock, revoke, scope change |
| Refresh token | Days to “until revoked” | Same blob, must persist rotations | Policy, unused window, Testing mode, rotation overwrite |
| Client secret | Until you rotate it | Credential fields | Leak, offboarding, project delete |
| Service-account key | Until you rotate it | Credential / secret manager | Key disable; no user consent to lose |
Google lists the ways a refresh token stops working: user revoke, unused for six months, Gmail-scoped password change, too many live refresh tokens, time-based access expired, admin restriction (admin_policy_enforced), or a Cloud session-control policy that surfaces as invalid_grant (refresh token expiration).
Google also caps 100 refresh tokens per Google Account per OAuth 2.0 client ID. The 101st invalidates the oldest without warning. That limit does not apply to service accounts. Reconnecting the same personal Gmail from five staging instances is how production wakes up to invalid_grant with no code change.
Microsoft Entra refresh tokens replace themselves on every use. Default lifetime is 90 days for most confidential-client scenarios, 24 hours for SPAs and email-OTP flows. The platform does not revoke the old refresh token on rotation — you must store the new one and drop the old (Refresh tokens). If two n8n workers race and the loser writes stale oauthTokenData, the next refresh uses a discarded grant.
Procedure — treat the refresh token as the asset:
- Confirm the grant type actually issues a refresh token (authorization code + offline access, not client credentials).
- After every successful refresh, persist the entire token payload, including a rotated refresh token if present.
- Never copy
oauthTokenDatainto a Set node, sticky note, or git-tracked JSON. - Alert when refresh itself fails (
invalid_grant,invalid_rapt), not only when the resource API returns 401. - Inventory “last proven refresh” monthly so a six-month unused Google grant does not surprise you.
The access token dying is normal. The refresh token dying is the outage.
Why does reconnect look fixed for a day?
Reconnect mints a new access token. The next hours succeed. Tomorrow (or day seven) the same loop returns because you never fixed the grant.
| What you did | What actually changed | What is still broken |
|---|---|---|
| Clicked Reconnect / Sign in | New access token, maybe a new refresh grant | Testing app, personal account, wrong status code |
| Re-saved the credential | Forced n8n to store oauthTokenData again | Vendor still returns 200 + body error |
| Rotated the client secret only | New secret on an old refresh token | Refresh may still be revoked |
| Added the user to Test users | Consent works for that human | Seven-day Testing clock still ticks |
What it costs: silent data gaps, duplicate “fixes,” and a board question about why the pipeline “keeps dying.” Across 500+ automations, the reconnect-and-hope loop is the most expensive credential habit I still see.
What you do instead: treat reconnect as incident mitigation, then run the root-cause checklist before you call it closed.
- Publishing status is Production (or Internal Workspace), not Testing
- Credential owner is a company principal, not a contractor Gmail
-
tokenExpiredStatusCodematches a captured vendor response - Refresh token is present and last-used inside the vendor unused window
- Expiry alert fired once and the workflow was paused
A green morning after reconnect is not a postmortem.
Personal OAuth vs service accounts — which belongs in production?
If the workflow outlives the employee’s laptop, it cannot depend on that employee’s interactive OAuth. Google says the same thing for server-to-server work: use a service account that belongs to the application, not an end user (service accounts). n8n’s Google credential docs split the same line — OAuth2 for user-shaped access, service account where the node supports it (Google credentials).
| Pattern | Use when | Failure mode |
|---|---|---|
| Personal Google / Microsoft OAuth | Prototypes, personal productivity | Offboarding, password reset, 2FA change, Testing 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 |
n8n notes that Gmail plus service accounts needs domain-wide delegation and is inconsistent; they recommend OAuth2 for the Gmail node. That is a product constraint, not permission to use the intern’s Gmail. Use a company-owned mailbox and a published (or Internal) app.
Decision list:
- Does the API support a service account or client-credentials app? Prefer that.
- If you need a user grant (Gmail, user Drive), is the user a company principal with a named backup?
- Is the OAuth client Internal (Workspace-only) or External-in-Production — not External-in-Testing?
- Will this graph still be correct if that human leaves on Friday?
Google also warns operators not to park user credentials on a server for long-running jobs when the customer can apply session-control policies. Those policies surface as invalid_grant with no browser left to re-auth (session control). That is the founder-laptop pattern with a policy name.
Google Testing mode and the seven-day grant
If the OAuth consent screen is External and publishing status is Testing, Google issues a refresh token that expires in 7 days, unless the only scopes are name / email / profile (userinfo.email, userinfo.profile, openid). That is Google’s rule, not n8n’s (refresh token expiration). n8n documents the same failure as “Google Cloud app becoming unauthorized” and the fix as reconnect — which restarts the seven-day clock (OAuth2 single service, consent screen).
| Consent posture | Refresh token life | Who can consent | Production-safe? |
|---|---|---|---|
| External + Testing | 7 days (non-profile scopes) | Test users only | No |
| External + In production | Until revoke / unused 6 months / policy | Any Google account (verification may apply) | Yes, with owner + inventory |
| Internal (Workspace) | No 7-day Testing clock | Users in the org | Yes, if the workflow stays in-org |
| Service account | No user refresh token | N/A — JWT → access token | Yes, where the API allows it |
Checklist when Google-connected workflows die weekly:
- Publishing status is In production (or User type is Internal), 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
- You are not minting a new refresh token from five clients against one Gmail (100-token cap)
Reconnect without leaving Testing is the “fixed for a day” pattern with a calendar. The calendar is seven days. Operators who reconnect every Monday have not fixed OAuth. They have scheduled it.
Microsoft refresh tokens rotate — persist the new one
Entra refresh tokens are bound to user + client, not to a single resource. They rotate: each refresh returns a new refresh token. Default inactive lifetime is 90 days for most app types; SPA redirect URIs expire refresh tokens in 24 hours (Refresh tokens). Token-lifetime policies for refresh tokens were retired 30 January 2021 — you do not configure a longer refresh life in a policy anymore (configurable token lifetimes). Sign-in frequency is a Conditional Access problem, not an n8n toggle.
| Entra event | What n8n sees | Operator move |
|---|---|---|
| Access token expired, refresh still good | 401 → refresh → retry | Nothing if tokenExpiredStatusCode is 401 |
| Refresh rotated, old value overwritten by a stale worker | Next refresh invalid_grant | One writer for oauthTokenData; pause siblings |
| 90-day unused / CAE revoke / password reset | Refresh fails | Re-consent the company account; do not loop |
| App registered as SPA | 24-hour refresh | Wrong client type for a server workflow |
| Conditional Access sign-in frequency | Interactive re-auth required | You cannot silently refresh past that policy |
Checklist for Microsoft-connected graphs:
- Client is a confidential / web app, not an SPA redirect, if this is a server workflow
- Latest refresh token is what n8n stores after every refresh
- Queue-mode workers share
N8N_ENCRYPTION_KEYso they read the same encrypted blob (custom encryption key) - Expiry alert classifies Entra
invalid_grantas auth, not as a generic HTTP error
Rotation is not optional. If you keep the first refresh token you ever saw, you will lose the grant on a quiet Tuesday.
What should I set on tokenExpiredStatusCode?
Set it to the HTTP status the resource API returns when the access token is expired. Default 401. Change it only when you have a captured response that says otherwise.
Token Expired Status Code: 401 # default
# set to 403 if your API uses 403 for expired access tokens
| Captured response | Field value | Extra work |
|---|---|---|
401 Unauthorized | 401 (default) | None |
403 Forbidden that means expired (not “missing scope”) | 403 | Prove it is expiry, not authorization |
200 + { "code": 602 } (Marketo-style) | Leave 401 | Branch on body; Stop and Error so the error workflow fires |
Token endpoint 400 invalid_grant | N/A | Pause. Re-consent. Status code will not save you |
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
- Do not set the field to 200. That would “refresh” on success.
Wrong status is the most common “n8n does not refresh” ticket that is not an n8n bug.
How do I alert on expiry before the CRM goes dark?
You alert on the refresh failing, on a rising 401 rate per credential, and on a calendar for known short grants — not on every failed enrichment item. Error email only fires when a node throws. A 200-with-dead-token never throws. A refresh that still works hides a Testing clock that expires Sunday.
Minimum expiry-alert contract:
| Signal | Fires when | Action | Page? |
|---|---|---|---|
errorClass=auth burst | N auth failures in M minutes on one credential | Pause siblings; one ticket | Yes if SEV1 path |
Refresh / invalid_grant | Token endpoint rejected the refresh | Pause; named owner reconnects the right account type | Yes |
| Testing-clock calendar | External + Testing credential age ≥ 5 days | Publish the app or migrate off Testing before day 7 | Morning ticket unless SEV1 |
| Unused-refresh calendar | Last proven refresh ≥ 150 days (Google six-month rule) | Staging job that touches the grant | No — scheduled |
| Heartbeat miss | Critical workflow did not run | See overnight playbook | Per that playbook |
Wire the throw path through one Error Trigger handler — n8n runs the error workflow you attach under Workflow Settings (handle errors gracefully). The alert body operators actually read lives in error workflows operators read. For auth, add three fields that generic HTTP alerts omit:
- Credential name (not the secret)
- Grant type (user OAuth / service account / PAT)
- Pause recommendation (yes if SEV1 or shared credential)
Checklist — expiry alerts that do not become noise:
- Classify
authseparately from429and schema - Deduplicate on credential name + hour, not on execution id
- Include first failure time, last failure time, and workflow list
- Auto-pause only a tagged SEV1 set after N failures — not every sandbox
- Heartbeat the “never ran” case; error workflows miss silence
- Calendar the 7-day Testing grant and the 6-month unused Google grant
Quiet is the enemy. Green checkmarks on last Tuesday’s execution do not prove tomorrow’s refresh works.
Incident response when credentials die overnight
Auth storms without pause burn rate limits and fill the log with the same poison. Pause first. Diagnose second. Reconnect third.
Runbook (paste into your ops doc):
- Confirm — error is 401/403/
invalid_grant, 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? Entra SPA client? worker overwrite?
- 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, publishing status, monitoring).
| Symptom | Likely cause | Do not |
|---|---|---|
| Dies every ~7 days | External + Testing | Reconnect and close the ticket |
| Dies after a hire’s last day | Personal OAuth | Blame n8n |
| Dies after you added a second worker | Stale refresh overwrite | Keep both writers |
| Dies with 200 + body error | No reactive refresh | Raise tokenExpiredStatusCode to 200 |
Dies with invalid_grant / invalid_rapt | Refresh revoked or session policy | Retry the resource API |
Severity: if money or a customer-facing system of record is on the other side of this credential, this is a page. Enrichment can wait until morning. That split is the overnight post; this runbook is the credential half.
How do I rotate secrets without downtime?
Prefer dual credentials. Create the new grant, prove it in staging, flip production node mappings, then revoke the old secret. In-place reconnect is fine for a true OAuth repair after a pause.
| 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 |
| Service-account key swap | Upload new key → prove → disable old key | Google does not keep a copy of the private key you downloaded |
Never paste client secrets into Slack. Never leave the old consumer key active “just in case” without a revoke date. Google’s own OAuth best practices: store user tokens securely, revoke and delete them when you no longer need them, and handle refresh invalidation as a first-class path (OAuth best practices).
Checklist:
- New credential has a name that says
prod-and the vendor - Staging graph uses the new credential for one full cycle
- Production nodes flip in one change window
- Old secret / refresh / key has a revoke timestamp
- Encryption key is unchanged across main and workers during the flip
Downtime is optional. Dual credentials make it optional on purpose.
How do offboarding and personal Google accounts break companies?
Personal OAuth dies when the human leaves, resets a password, or loses consent. The workflows stay active. Failures look like “n8n is flaky” for a week.
How companies break:
- Intern connects Gmail/Sheets with their user OAuth.
- Intern leaves; refresh is revoked (or Google expires an unused grant at six months).
- Nobody owns the credential; workflows stay active.
- The 401 loop runs overnight. Nobody paused it.
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
- Disable the departed user’s Google / Entra account and inventory n8n grants that used it
| Offboarding event | What happens to the grant | What you do the same day |
|---|---|---|
| Workspace account suspended | Refresh revoked | Pause graphs; reconnect on a service account or remaining owner |
| Personal Gmail password change with Gmail scopes | Google invalidates that refresh | Same — and stop using personal Gmail |
| Contractor off Slack, still in n8n | Credential still works until they revoke | Remove n8n access; rotate anything they could export |
| Shared “founder’s Google” with 8 staging reconnects | Oldest refresh tokens silently die (100-cap) | One production client; delete unused OAuth clients |
If you cannot name the human who owns prod-google-sheets-ops and the human who covers vacation, you do not have a production credential. You have a time bomb.
Where should credentials live?
In the n8n Credentials store, encrypted with a key you control and back up — or in a secret manager you inject at deploy. Not on the canvas. n8n encrypts credential data with N8N_ENCRYPTION_KEY; in queue mode every worker must use the same key or they cannot read the blob (set a custom encryption key).
| 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) |
| Workflow static data as a “manual refresh” | No — that was the rejected workaround on #32423 |
If someone needs a value to debug, grant time-boxed access to the credential UI — do not copy secrets into the graph. Google: unused OAuth clients that sit idle for six months can be deleted automatically; audit clients the same way you audit n8n credentials (best practices).
What belongs in a credential inventory?
A row per production credential. Review monthly. Orphans are incidents waiting for a Friday.
| Field | Example |
|---|---|
| Credential name in n8n | prod-google-sheets-ops |
| Vendor / app | Google Sheets — company Cloud project |
| Grant type | OAuth (workspace) / service account / PAT |
| Publishing / client type | External + Production / Internal / confidential |
| Owner + backup | Jamie / Alex |
| Workflows using it | invoice-sync, lead-enrichment |
| SEV if dead | SEV1 / SEV2 |
tokenExpiredStatusCode | 401 |
| Last proven refresh | 2026-08-10 staging job |
| Unused-refresh risk | Days since last refresh (Google: 180 is dead) |
| Offboarding risk | Personal? yes/no |
| Expiry alert wired | Yes — credential-scoped, deduped |
Monthly pass:
- Open the inventory. Kill rows with no owner.
- Run the staging job that exercises each SEV1 credential.
- Confirm Testing apps are gone from production graphs.
- Confirm last proven refresh is inside the vendor unused window.
- Confirm the error workflow still classifies
authand still names the credential.
If you cannot produce this table in ten minutes, you will debug expiry in Slack instead.
How do I pair auth failures with the error workflow?
Auth is a first-class error class next to schema and rate-limit. Attach one Error Trigger handler under Settings → Error workflow on every production flow. New graphs do not inherit it (handle errors gracefully).
- Error Trigger / workflow-level error handler catches the failure.
- Classify
errorClass=authwhen status is 401/403 or the body/token error isinvalid_grant/invalid_token. - Write a DLQ-style row with credential name (not the secret) and the raw status/body.
- Notify with execution link, credential name, owner, and a “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. Continue on Fail is a branch, not a mute: if you swallow the 401 to keep the graph green, the expiry alert never fires.
| Setting | Auth-safe? | Why |
|---|---|---|
| Error workflow attached | Required | Without it, only the execution list knows |
| Continue on Fail on the OAuth node | No, unless you branch to Stop and Error | Swallows the only refresh signal |
| Retry on fail, unbounded | No | 401 storm + vendor rate limit |
| Retry on fail, 2x, then error workflow | Acceptable for transient 401s that refresh | Still pause on invalid_grant |
| Manual Execute to “test” the handler | Insufficient | Error Trigger does not fire on manual runs |
Prove the handler on an activated Schedule or Webhook path. Then prove the auth class with a revoked staging credential. If that test does not produce exactly one alert and a pause recommendation, you do not have expiry coverage.
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. Treat reconnect as mitigation, then fix publishing status, account type, tokenExpiredStatusCode, or worker overwrite before you call it closed.
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. It does not read expires_in, and it does not parse a 200 body.
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. Keep N8N_ENCRYPTION_KEY stable across main and workers during the flip.
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. Enrichment can wait; money paths page.
How do offboarding and personal Google accounts break companies?
Personal OAuth dies when the human leaves, resets a password, or loses consent. Google also expires unused refresh tokens at six months and Testing grants at seven days. 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, Set-node secrets, and workflow static data become leaks and unrotatable debt. Grant time-boxed UI access for debug; do not copy the refresh token into the graph.
CTA
Treat OAuth like a production dependency with an owner, a refresh-token inventory, and an expiry alert — 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 the audit.
What questions does this article answer?
- 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. Treat reconnect as mitigation, then fix publishing status, account type, `tokenExpiredStatusCode`, or worker overwrite before you call it closed.
- 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. It does not read `expires_in`, and it does not parse a 200 body.
- 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. Keep `N8N_ENCRYPTION_KEY` stable across main and workers during the flip.
- 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. Enrichment can wait; money paths page.
- How do offboarding and personal Google accounts break companies?
- Personal OAuth dies when the human leaves, resets a password, or loses consent. Google also expires unused refresh tokens at six months and Testing grants at seven days. 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, Set-node secrets, and workflow static data become leaks and unrotatable debt. Grant time-boxed UI access for debug; do not copy the refresh token into the graph.
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.