BrandPilot — LinkedIn Token Expired

Trigger: LinkedIn posts fail to publish. Users see “LinkedIn token expired” or “LinkedIn authorization failed” error in the BrandPilot UI.

Data location: social_accounts table in Supabase — status, token_expires_at, access_token columns.


1. Confirm

Check affected accounts in Supabase

-- Run in Supabase SQL editor or via service role API:
SELECT id, user_id, provider, status, token_expires_at
FROM social_accounts
WHERE provider = 'linkedin'
  AND (status = 'expired' OR token_expires_at < now())
ORDER BY token_expires_at ASC;

If rows are returned → tokens are expired. Proceed below.


2. Root cause

LinkedIn OAuth tokens expire after 60 days (standard) or 2 hours (access token; refreshed via refresh token).

Possible causes:

  1. User’s access token expired and refresh failed (most common)
  2. Refresh token expired (60-day lifetime) — user must re-authorize
  3. LinkedIn app credentials changed (LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET) — all tokens invalidated
  4. LinkedIn app removed from our LinkedIn Developer account — all tokens invalidated

3. Fix

Fix A: User re-authorizes (most common fix)

Each affected user must reconnect their LinkedIn account:

  1. User navigates to Settings → Social Accounts in BrandPilot
  2. Clicks “Reconnect LinkedIn” next to the expired account
  3. Completes the LinkedIn OAuth flow
  4. The /api/auth/linkedin/callback route handles token storage

No admin action needed — this is fully self-service.

To notify affected users (optional, admin):

  • Query social_accounts for expired tokens, get user_id, look up email in profiles table
  • Send notification email via Resend: POST /api/admin/notify-linkedin-reauth (if implemented)

Fix B: LinkedIn app credentials changed

If the LinkedIn Developer app’s Client ID or Client Secret was rotated:

  1. Update in SOPS:
    sops -d C:\code_2026\p24-infra\secrets\brandpilot.env.sops > /tmp/brandpilot.env
    # Edit: update LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET
    sops -e /tmp/brandpilot.env > C:\code_2026\p24-infra\secrets\brandpilot.env.sops
    
  2. Commit and push to a PR → secrets-sync.yml syncs to Vercel on merge
  3. Trigger Vercel redeployment
  4. All users must re-authorize LinkedIn (Fix A above)

Fix C: LinkedIn app suspended or removed

If the LinkedIn Developer app was suspended:

  1. Go to https://developer.linkedin.com/ → sign in with admin account
  2. Review app status — restore if suspended or recreate if deleted
  3. Update LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET (Fix B above)
  4. All users must re-authorize LinkedIn (Fix A above)

This is always a human action — apply human-action label to related issue.


4. LinkedIn OAuth re-auth flow (technical reference)

The re-auth flow entry point:

GET /api/auth/linkedin
  └─ redirects to LinkedIn OAuth authorize URL with:
       client_id=LINKEDIN_CLIENT_ID
       scope=openid profile email w_member_social
       redirect_uri=https://p24-brandpilot.zintegrowana.online/api/auth/linkedin/callback
       state=<csrf-token>

GET /api/auth/linkedin/callback?code=...&state=...
  └─ exchanges code for access_token + refresh_token
  └─ upserts into social_accounts table:
       { provider: 'linkedin', access_token, refresh_token, token_expires_at, status: 'active' }

If callback returns an error, check Vercel runtime logs for the /api/auth/linkedin/callback function.


5. Escalate to human when

  • LinkedIn app has been suspended or deleted → restore/recreate in LinkedIn Developer portal
  • LinkedIn API rate limits hit → wait or contact LinkedIn support
  • App credentials need rotation → SOPS update + Vercel re-sync (partially automated via secrets-sync.yml)

6. Verify fix

After user re-authorizes, trigger a test LinkedIn post from the BrandPilot UI. Also verify status = 'active' and token_expires_at > now() + interval '1 day' in social_accounts.