Skip to content

OAuth 2.0 apps

Building an app that other Pennylane companies install? Pennylane supports the standard authorization-code flow. Ask Pennylane for partner credentials (clientId, clientSecret) first.

The flow

<?php

use Pennylane\Sdk\OAuth\OAuthApp;

$app = new OAuthApp(
    clientId: '...',
    clientSecret: '...',
    redirectUri: 'https://yourapp.example.com/oauth/callback',
);

// 1. Send the user to Pennylane's consent screen
$url = $app->authorizationUrl(
    scopes: ['customer_invoices:all', 'customers:all'],
    state: $antiCsrfToken,
);

// 2. Pennylane redirects back with ?code=...; exchange it
$tokens = $app->exchangeCode($code);

// 3. Use the access token like a regular API token
use Pennylane\Sdk\Pennylane;

$client = new Pennylane(apiToken: $tokens->accessToken);

Token lifetime and refresh

Access tokens live 24 hours ($tokens->expiresIn === 86400). Refresh before expiry:

$newTokens = $app->refresh($storedRefreshToken);
save($newTokens->accessToken, $newTokens->refreshToken); // persist IMMEDIATELY

Refresh Token Rotation

Pennylane rotates refresh tokens: every refresh invalidates both previous tokens. Two rules follow:

  1. Never run two refreshes concurrently for the same connection. OAuthApp does not serialize calls for you: if two requests race to refresh the same stored token, one of them will present an already-rotated refresh token and fail. Guard the refresh with your own lock (a database row lock, a distributed mutex, and so on), whether within one process or across several.
  2. Persist the new pair before using it. If you crash after refreshing but before saving, the stored refresh token is dead and the user must re-authorize.

When a refresh token is invalid (revoked, rotated away, expired), the token endpoint answers 401 invalid_grant and the SDK raises AuthenticationException: send the user through the authorization flow again.

The 2026 scopes migration

In January 2026, Pennylane replaced the legacy ledger scope with granular scopes and auto-added the new scopes to existing OAuth apps. If your app was created before that, re-authenticating your users once ensures their consents match the new scope model.