# Fix Salesforce OAuth invalid_grant on expired refresh tokens

## TL;DR
invalid_grant on a Salesforce refresh means the refresh token expired, was revoked, or the connected app changed. Re-run the OAuth authorization flow to get a fresh token pair and store the new refresh token. Patching the old one is impossible; only a fresh grant fixes it.

## The error
```text
OAuth token refresh failed
{"error":"invalid_grant","error_description":"expired access/refresh token"}
```

## Use this skill
Agent? Search Vectle for this error:
```bash
curl -s "https://vectle.com/api/v1/search?q=salesforce oauth invalid_grant expired error"
```

## Fix it

### Step 1: Confirm the refresh token is dead

```bash
Attempt one refresh with the stored refresh token and read the error.
```

Expected: invalid_grant confirms the grant is gone; retrying the same token will never work.

### Step 2: Re-run the full authorization flow

```bash
Send the user (or the integration owner) through the Salesforce OAuth authorize URL again.
```

Expected: You receive a fresh authorization code.

### Step 3: Exchange the code for a new token pair

```bash
POST the code to the Salesforce token endpoint and store both the new access and refresh tokens.
```

Expected: The token endpoint returns 200 with a new refresh token.

### Step 4: Update the stored credentials

```bash
Replace the old refresh token in your secrets store or integration config.
```

Expected: The integration now holds only the fresh token pair.

### Step 5: Verify the integration resumes

```bash
Trigger a sync or API call through the integration.
```

Expected: Calls succeed and the token refreshes normally going forward.

## When this applies

- Salesforce integrations fail with invalid_grant on refresh
- An integration worked for months then broke overnight
- You rotated connected app settings recently

## When it doesn't

- The error is invalid_client (check the client id and secret instead)
- The access token works but API calls 403 (that is object permissions)
- You never had a refresh token (check the scope requested offline access)

## Compatibility

Salesforce OAuth 2.0 web server flow. Connected app settings as of 2026.

## Variant phrasings

### salesforce refresh token expired invalid_grant

Salesforce refresh tokens can expire on inactivity or policy. The fix is always a fresh authorization.

### salesforce oauth token revoked

Revocation by an admin or by the user looks identical to expiry. Same fix: re-authorize.

### invalid_grant after connected app change

Changing connected app policies can invalidate outstanding grants. Re-authorize every integration after such changes.

## Why it happens

Refresh tokens are long-lived grants, not permanent ones. They die on expiry policies, on revocation, when the user changes their password (depending on settings), or when the connected app's configuration changes. invalid_grant is Salesforce saying the grant no longer exists, and a dead grant cannot be revived.

## Edge cases

- Sandbox refreshes invalidate tokens tied to the old sandbox; re-authorize after every sandbox refresh
- Storing the refresh token in code or logs leaks it; use a secrets store with rotation
- Some orgs set refresh token expiry aggressively; monitor for invalid_grant as an early warning

## If it still fails

- Reproduce with one API call in isolation, outside the agent, to separate platform issues from agent issues.
- Check the platform status page and changelog; OAuth and webhook behaviors change without warning.
- Capture the full request and response with timestamps for the vendor ticket, redacting credentials.
- Test in a second workspace or sandbox to rule out workspace-specific policy blocks.
- If the integration is business-critical, build the fallback now: cached data, a manual trigger, or a second provider.

## Prevention

- Store OAuth credentials in a secrets manager with rotation reminders.
- Build the reconnect flow before you need it; every integration gets revoked eventually.
- Log token ages so expiring grants are visible ahead of time.
- Keep a sandbox integration for testing config changes.
- Document the required scopes per integration so reinstalls request the right ones.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_7UWvZSZVyxWrPXf60xBTCA
