Spurlock Studios
Contact
Share LinkedIn X
Two clipped paper packets. Thesis: OAUTH TOKENS WILL EXPIRE STOP.

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.

StepBehavior
Request with access tokenHTTP Request / node calls the API
Response status == tokenExpiredStatusCodeTrigger refresh (or client-credentials fetch)
Refresh succeedsPersist new oauthTokenData; retry the request
Refresh fails / wrong statusError 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:

  1. Did the API return 401 (or your configured code) on the expired access token?
  2. Did the provider issue a refresh token at all?
  3. Did n8n persist the new refresh token after rotation?
  4. 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 signaln8n generic OAuth2What you do
HTTP 401Refresh + retry (default)Prove it in staging with a short-lived token
HTTP 403 meaning expiredNo refresh unless you set the fieldSet 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 endpointRefresh already failedPause. 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 tokenExpiredStatusCode or 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).

ArtifactTypical lifeStored where in n8nDies when
Access tokenMinutes to hoursInside oauthTokenDataClock, revoke, scope change
Refresh tokenDays to “until revoked”Same blob, must persist rotationsPolicy, unused window, Testing mode, rotation overwrite
Client secretUntil you rotate itCredential fieldsLeak, offboarding, project delete
Service-account keyUntil you rotate itCredential / secret managerKey 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:

  1. Confirm the grant type actually issues a refresh token (authorization code + offline access, not client credentials).
  2. After every successful refresh, persist the entire token payload, including a rotated refresh token if present.
  3. Never copy oauthTokenData into a Set node, sticky note, or git-tracked JSON.
  4. Alert when refresh itself fails (invalid_grant, invalid_rapt), not only when the resource API returns 401.
  5. 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 didWhat actually changedWhat is still broken
Clicked Reconnect / Sign inNew access token, maybe a new refresh grantTesting app, personal account, wrong status code
Re-saved the credentialForced n8n to store oauthTokenData againVendor still returns 200 + body error
Rotated the client secret onlyNew secret on an old refresh tokenRefresh may still be revoked
Added the user to Test usersConsent works for that humanSeven-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
  • tokenExpiredStatusCode matches 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).

PatternUse whenFailure mode
Personal Google / Microsoft OAuthPrototypes, personal productivityOffboarding, password reset, 2FA change, Testing expiry
Workspace / company OAuth app + shared mailboxTeam ops with consent policyStill tied to human approval if mis-scoped
Service account / server-to-serverProduction backends that support itKey rotation discipline required
Vendor API key / PATWhen OAuth is optionalKey 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:

  1. Does the API support a service account or client-credentials app? Prefer that.
  2. If you need a user grant (Gmail, user Drive), is the user a company principal with a named backup?
  3. Is the OAuth client Internal (Workspace-only) or External-in-Production — not External-in-Testing?
  4. 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 postureRefresh token lifeWho can consentProduction-safe?
External + Testing7 days (non-profile scopes)Test users onlyNo
External + In productionUntil revoke / unused 6 months / policyAny Google account (verification may apply)Yes, with owner + inventory
Internal (Workspace)No 7-day Testing clockUsers in the orgYes, if the workflow stays in-org
Service accountNo user refresh tokenN/A — JWT → access tokenYes, 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 eventWhat n8n seesOperator move
Access token expired, refresh still good401 → refresh → retryNothing if tokenExpiredStatusCode is 401
Refresh rotated, old value overwritten by a stale workerNext refresh invalid_grantOne writer for oauthTokenData; pause siblings
90-day unused / CAE revoke / password resetRefresh failsRe-consent the company account; do not loop
App registered as SPA24-hour refreshWrong client type for a server workflow
Conditional Access sign-in frequencyInteractive re-auth requiredYou 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_KEY so they read the same encrypted blob (custom encryption key)
  • Expiry alert classifies Entra invalid_grant as 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 responseField valueExtra work
401 Unauthorized401 (default)None
403 Forbidden that means expired (not “missing scope”)403Prove it is expiry, not authorization
200 + { "code": 602 } (Marketo-style)Leave 401Branch on body; Stop and Error so the error workflow fires
Token endpoint 400 invalid_grantN/APause. 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:

SignalFires whenActionPage?
errorClass=auth burstN auth failures in M minutes on one credentialPause siblings; one ticketYes if SEV1 path
Refresh / invalid_grantToken endpoint rejected the refreshPause; named owner reconnects the right account typeYes
Testing-clock calendarExternal + Testing credential age ≥ 5 daysPublish the app or migrate off Testing before day 7Morning ticket unless SEV1
Unused-refresh calendarLast proven refresh ≥ 150 days (Google six-month rule)Staging job that touches the grantNo — scheduled
Heartbeat missCritical workflow did not runSee overnight playbookPer 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:

  1. Credential name (not the secret)
  2. Grant type (user OAuth / service account / PAT)
  3. Pause recommendation (yes if SEV1 or shared credential)

Checklist — expiry alerts that do not become noise:

  • Classify auth separately from 429 and 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):

  1. Confirm — error is 401/403/invalid_grant, not schema or 429.
  2. Pause affected production workflows (and siblings sharing the credential).
  3. Alert once with credential name, workflows, first/last failure time — not a page per execution.
  4. Diagnose — refresh token present? status code mismatch? personal account? Google Testing mode? Entra SPA client? worker overwrite?
  5. Rotate / reconnect using the correct account type; verify with a single staging or pinned-data run.
  6. Resume workflows deliberately; watch the next scheduled/webhook cycle.
  7. Write the postmortem line — root cause + permanent fix (service account, status code, publishing status, monitoring).
SymptomLikely causeDo not
Dies every ~7 daysExternal + TestingReconnect and close the ticket
Dies after a hire’s last dayPersonal OAuthBlame n8n
Dies after you added a second workerStale refresh overwriteKeep both writers
Dies with 200 + body errorNo reactive refreshRaise tokenExpiredStatusCode to 200
Dies with invalid_grant / invalid_raptRefresh revoked or session policyRetry 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.

ApproachStepsNotes
Dual credential cutoverCreate new credential → point staging → flip production nodes → revoke oldBest for API keys / second OAuth app
In-place reconnectPause → reconnect → single test → resumeFine for true OAuth refresh repair
Env / credential overwriteInject via supported overwrite mechanismsKeep encryption key/backup process intact
Service-account key swapUpload new key → prove → disable old keyGoogle 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:

  1. Intern connects Gmail/Sheets with their user OAuth.
  2. Intern leaves; refresh is revoked (or Google expires an unused grant at six months).
  3. Nobody owns the credential; workflows stay active.
  4. 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 eventWhat happens to the grantWhat you do the same day
Workspace account suspendedRefresh revokedPause graphs; reconnect on a service account or remaining owner
Personal Gmail password change with Gmail scopesGoogle invalidates that refreshSame — and stop using personal Gmail
Contractor off Slack, still in n8nCredential still works until they revokeRemove n8n access; rotate anything they could export
Shared “founder’s Google” with 8 staging reconnectsOldest 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).

PlaceAllowed?
n8n Credentials storeYes — default
Secret manager → injected at deployYes — for self-hosted discipline
Canvas sticky notes / Set node hardcodesNo
Shared Google Doc “for the team”No
Git repoNo (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.

FieldExample
Credential name in n8nprod-google-sheets-ops
Vendor / appGoogle Sheets — company Cloud project
Grant typeOAuth (workspace) / service account / PAT
Publishing / client typeExternal + Production / Internal / confidential
Owner + backupJamie / Alex
Workflows using itinvoice-sync, lead-enrichment
SEV if deadSEV1 / SEV2
tokenExpiredStatusCode401
Last proven refresh2026-08-10 staging job
Unused-refresh riskDays since last refresh (Google: 180 is dead)
Offboarding riskPersonal? yes/no
Expiry alert wiredYes — credential-scoped, deduped

Monthly pass:

  1. Open the inventory. Kill rows with no owner.
  2. Run the staging job that exercises each SEV1 credential.
  3. Confirm Testing apps are gone from production graphs.
  4. Confirm last proven refresh is inside the vendor unused window.
  5. Confirm the error workflow still classifies auth and 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).

  1. Error Trigger / workflow-level error handler catches the failure.
  2. Classify errorClass=auth when status is 401/403 or the body/token error is invalid_grant / invalid_token.
  3. Write a DLQ-style row with credential name (not the secret) and the raw status/body.
  4. Notify with execution link, credential name, owner, and a “paused?” recommendation.
  5. 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.

SettingAuth-safe?Why
Error workflow attachedRequiredWithout it, only the execution list knows
Continue on Fail on the OAuth nodeNo, unless you branch to Stop and ErrorSwallows the only refresh signal
Retry on fail, unboundedNo401 storm + vendor rate limit
Retry on fail, 2x, then error workflowAcceptable for transient 401s that refreshStill pause on invalid_grant
Manual Execute to “test” the handlerInsufficientError 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.

FAQ

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.
Sources

Last reviewed

More from this lane

Automation

All →
Book the audit