Connect
v2025-08-28Support

Authentication

Cleavr Connect uses OAuth 2.0 for secure authentication. This allows your platform to act on behalf of your users while maintaining security and control.

Warning

Security Notice: Never expose your client_secret in client-side code or public repositories.

OAuth 2.0 Flow

OAuth 2.0 authentication flow between your platform and Cleavr Connect.

UserYour PlatformCleavr ConnectInitiates Recovery SetupRedirect to /oauth/authorizeShows Consent ScreenApproves AccessRedirect with codeExchange code for tokenReturns access_tokenMake API calls with token

Implementation Guide

Step 1: Register Your Platform

Contact us at this form to register your platform. You'll receive:

json
{
"client_id": "plat_live_a1b2c3d4e5f6",
"client_secret": "sk_live_xxxxxxxxxxxxx",
"webhook_secret": "whsec_xxxxxxxxxxxxx"
}

Step 2: Authorization Request

Redirect users to our authorization endpoint:

Node.js
const authorizationUrl = new URL('https://api.cleavr.fr/oauth/authorize'); authorizationUrl.searchParams.append('client_id', CLIENT_ID); authorizationUrl.searchParams.append('redirect_uri', 'https://yourapp.com/callback'); authorizationUrl.searchParams.append('response_type', 'code'); authorizationUrl.searchParams.append('scope', 'receivables:write users:write webhooks:read'); authorizationUrl.searchParams.append('state', generateSecureRandomState());// Redirect user window.location.href = authorizationUrl.toString();

Available Scopes

ScopeDescription
users:readRead user information
users:writeCreate and update users
receivables:readView receivables
receivables:writeCreate and manage receivables
webhooks:readView webhook events
webhooks:writeConfigure webhooks
reports:readAccess recovery reports
commission:readView commission rates
Info

Best Practice: Request only the scopes you need. Start with minimal permissions and expand as needed.

Step 3: Handle Callback

After user authorization, Cleavr redirects back to your redirect_uri:

Command Line
https://yourapp.com/callback?code=auth_code_xxxxx&state=your_state_value

Verify the state parameter to prevent CSRF attacks:

javascript
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state matches what you sent
if (state !== session.oauth_state) {
return res.status(400).send('Invalid state parameter');
}
// Exchange code for token
const token = await exchangeCodeForToken(code);
// Store token securely
await storeTokenForUser(req.user.id, token);
res.redirect('/dashboard');
});

Step 4: Exchange Code for Token

Exchange the authorization code for an access token:

Node.js
async function exchangeCodeForToken(code) { const response = await fetch('https://api.cleavr.fr/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'authorization_code', code: code, redirect_uri: 'https://yourapp.com/callback', client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }), });return response.json(); }

Response:

json
{
"access_token": "clv_tok_xxxxxxxxxxxxx",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "clv_ref_xxxxxxxxxxxxx",
"scope": "receivables:write users:write webhooks:read"
}

Step 5: Make Authenticated Requests

Include the access token in the Authorization header:

Node.js
const response = await fetch('https://api.cleavr.fr/v1/receivables', { method: 'POST', headers: { 'Authorization': Bearer ${accessToken}, 'Content-Type': 'application/json', 'Cleavr-Version': '2025-08-28' }, body: JSON.stringify(receivableData) });

Token Management

Refresh Tokens

Access tokens expire after 1 hour. Use the refresh token to get a new access token:

Node.js
async function refreshAccessToken(refreshToken) { const response = await fetch('https://api.cleavr.fr/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }), });return response.json(); }

Token Storage Best Practices

Warning

Security Critical: Store tokens encrypted and never in client-side storage.

javascript
// DO: Store encrypted in database
await db.tokens.create({
user_id: userId,
access_token: encrypt(token.access_token),
refresh_token: encrypt(token.refresh_token),
expires_at: new Date(Date.now() + token.expires_in * 1000)
});
// DON'T: Store in localStorage or cookies
localStorage.setItem('token', token); // ❌ Never do this

Automatic Token Refresh

Implement automatic token refresh:

javascript
class CleavrClient {
constructor(tokenStore) {
this.tokenStore = tokenStore;
}
async makeRequest(url, options = {}) {
let token = await this.tokenStore.getToken();
// Check if token needs refresh
if (this.isTokenExpired(token)) {
token = await this.refreshToken(token.refresh_token);
await this.tokenStore.saveToken(token);
}
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token.access_token}`
}
});
// Handle 401 - token might have been revoked
if (response.status === 401) {
token = await this.refreshToken(token.refresh_token);
await this.tokenStore.saveToken(token);
// Retry request with new token
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token.access_token}`
}
});
}
return response;
}
isTokenExpired(token) {
return Date.now() >= token.expires_at - 60000; // Refresh 1 min early
}
}

Revoking Access

Users can revoke access at any time. Handle revocation gracefully:

javascript
// Revoke token programmatically
async function revokeToken(token) {
await fetch('https://api.cleavr.fr/oauth/revoke', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
token: token,
token_type_hint: 'access_token',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
}),
});
}

Error Handling

Common OAuth Errors

ErrorDescriptionSolution
invalid_requestMissing required parametersCheck all required params are included
invalid_clientInvalid client_id or client_secretVerify your credentials
invalid_grantInvalid or expired authorization codeRequest a new authorization
unauthorized_clientClient not authorized for grant typeContact support
unsupported_grant_typeInvalid grant_type parameterUse authorization_code or refresh_token
invalid_scopeRequested scope is invalidCheck available scopes

Error Response Example

json
{
"error": "invalid_grant",
"error_description": "The provided authorization code is invalid or has expired",
"error_uri": "https://docs.cleavr.fr/connect/errors#invalid_grant"
}

Security Considerations

For single-page applications, use PKCE for added security:

javascript
// Generate code verifier and challenge
const codeVerifier = generateRandomString(128);
const codeChallenge = await sha256(codeVerifier);
// Include in authorization request
authUrl.searchParams.append('code_challenge', codeChallenge);
authUrl.searchParams.append('code_challenge_method', 'S256');
// Include verifier when exchanging code
body.append('code_verifier', codeVerifier);

2. State Parameter

Always use a cryptographically secure random state:

javascript
function generateState() {
return crypto.randomBytes(32).toString('hex');
}

3. Redirect URI Validation

  • Use exact match validation
  • Always use HTTPS in production
  • Register all redirect URIs with Cleavr

4. Token Rotation

Implement token rotation for enhanced security:

javascript
// Each refresh invalidates the previous refresh token
const newTokens = await refreshAccessToken(currentRefreshToken);
// currentRefreshToken is now invalid

Testing

Test Environment

Use test credentials in development:

javascript
const config = {
development: {
apiUrl: 'https://api-test.cleavr.fr',
clientId: 'plat_test_xxxxx',
clientSecret: 'sk_test_xxxxx'
},
production: {
apiUrl: 'https://api.cleavr.fr',
clientId: 'plat_live_xxxxx',
clientSecret: 'sk_live_xxxxx'
}
};

Test User Flow

  1. Use test mode credentials
  2. Create test users with prefix test_
  3. Submit test receivables
  4. Verify webhook delivery
  5. Check token refresh flow

Code Examples

Node.js
const { CleavrConnect } = require('@cleavr/connect-node');const client = new CleavrConnect({ clientId: process.env.CLEAVR_CLIENT_ID, clientSecret: process.env.CLEAVR_CLIENT_SECRET, });// Get authorization URL const authUrl = client.oauth.getAuthorizationUrl({ redirectUri: 'https://yourapp.com/callback', scope: ['receivables:write', 'users:write'], state: 'unique_state_value' });

Next Steps

Once authentication is working:

  1. Set up User Onboarding
  2. Configure Webhooks
  3. Start Sending Receivables