Custom OAuth providers
Register OAuth apps Leash hasn't pre-built — Slack, Notion, custom OIDC, your company's SSO, anything with a standard OAuth 2.0 flow. Apps in your org get a fresh access token through one SDK call. client_secret stays out of your app code.
1. Register the provider (once, on the dashboard)
On the org page, the “Custom OAuth providers” card walks you through:
- A slug (used by your app to refer to it:
slack,notion, …) - Display name and OAuth authorize / token URLs
- Default scopes
- Your
client_idandclient_secret— encrypted with your org's KMS key before storage
Reserved slugs (gmail, google_calendar, google_drive, etc.) are taken — they map to built-in providers.
2. Send users through the OAuth flow
Same as built-in providers — redirect to Leash's connect endpoint with the slug; Leash handles the OAuth round-trip and stores the user's tokens.
// In your app, redirect the user to:const url = client.getConnectUrl('slack', '/post-connect-page')// e.g. <a href={url}>Connect Slack</a>
3. Get the user's access token in your app
Once the user has connected, every app in your org can pull a fresh access token to call the third-party API directly. Refresh-on-expiry happens transparently on the platform side — no per-app handler.
const slackToken = await client.getAccessToken('slack')await fetch('https://slack.com/api/chat.postMessage', {method: 'POST',headers: {Authorization: `Bearer ${slackToken}`,'Content-Type': 'application/json',},body: JSON.stringify({ channel: '#general', text: 'hello' }),})
Failure modes
not_connected— the user hasn't authorized this provider yet. Send them through the connect flow (step 2). The error includes aconnectUrlyou can redirect to.token_expired— refresh failed (the user revoked access, or the upstream provider rotated something). Treat the same asnot_connected.upgrade_required— registering custom OAuth providers requires the Growth plan. Built-in providers (Gmail, Calendar, Drive) work on every plan.