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:
- Never run two refreshes concurrently for the same connection.
OAuthAppdoes 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. - 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.