Authentication
Learn how to authenticate your API requests.

Flutterwave utilizes OAuth 2.0 to authenticate API requests. With OAuth 2.0, you can connect your application to our servers without using your keys or sensitive information. This guide explains how to generate and use tokens to authenticate your requests.
Using OAuth2.0
To authenticate API requests with OAuth 2.0, you'll need to generate an access_token
by sending a POST
request to the OAuth 2.0 token endpoint. You must include your client_id
, client_secret
, and grant_type
in the request.
Authentication on Sandbox and Production
This process applies to both the sandbox and production environments. Make sure to use the correct credentials for each environment to obtain a valid token.
curl -X POST 'https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id={{CLIENT_ID}}' \
--data-urlencode 'client_secret={{CLIENT_SECRET}}' \
--data-urlencode 'grant_type=client_credentials'
A successful request will return a response containing the access_token
, expires_in
, token_type
, not-before-policy
, and scopes
.
Here is an example response:
{
"access_token": "SAMPLE_TOKEN",
"expires_in": 600,
"refresh_expires_in": 0,
"token_type": "Bearer",
"not-before-policy": 0,
"scope": "profile email"
}
You can then use the access token to make authorized requests to any of our API endpoints:
# Sample cURL request to the payment gateway's API endpoint
curl -X GET https://api.flutterwave.cloud/{{ENVIRONMENT}}/endpoint \
-H "Authorization: Bearer {{ACCESS_TOKEN}}" \
-H "Content-Type: application/json"
Managing your Access Token
Your access token grants access to your Flutterwave account. Keep it confidential and store it securely on your server, ideally as an environment variable. Do not commit it to your Git repository or expose it in frontend code.
Refreshing your Access Token
Tokens expire periodically to help reduce security risks. Each token is valid for 10 minutes (as indicated by the expires_in
field), so you should refresh it before it expires to maintain uninterrupted access.
We recommend refreshing the token at least one minute before it expires. Below are example implementations:
const axios = require('axios');
let accessToken = null;
let expiresIn = 0; // token expiry time in seconds
let lastTokenRefreshTime = 0;
async function refreshToken() {
try {
const response = await axios.post(
'https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token',
new URLSearchParams({
client_id: '<CLIENT_ID>',
client_secret: '<CLIENT_SECRET>',
grant_type: 'client_credentials'
}),
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}
);
accessToken = response.data.access_token;
expiresIn = response.data.expires_in;
lastTokenRefreshTime = Date.now();
console.log('New Token:', accessToken);
console.log('Expires in:', expiresIn, 'seconds');
} catch (error) {
console.error('Error refreshing token:', error.response ? error.response.data : error.message);
}
}
async function ensureTokenIsValid() {
const currentTime = Date.now();
const timeSinceLastRefresh = (currentTime - lastTokenRefreshTime) / 1000; // convert to seconds
const timeLeft = expiresIn - timeSinceLastRefresh;
if (!accessToken || timeLeft < 60) { // refresh if less than 1 minute remains
console.log('Refreshing token...');
await refreshToken();
} else {
console.log(`Token is still valid for ${Math.floor(timeLeft)} seconds.`);
}
}
// Example usage: Call `ensureTokenIsValid` before making API requests
setInterval(ensureTokenIsValid, 5000); // check every 5 seconds
#using .env variables for client and secret.
class AuthManager:
def __init__(self):
self.credentials = []
self.load_credentials()
def load_credentials(self):
while True:
client_id = os.getenv(f"FLW_CLIENT_ID")
client_secret = os.getenv(f"FLW_CLIENT_SECRET")
if not client_id or not client_secret:
break
self.credentials.append({
"client_id": client_id,
"client_key": client_secret,
"access_token": None,
"expiry": None
})
indx += 1
def get_credentials(self):
return self.credentials
def generate_access_token(self):
idx = 0
header = {
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"client_id": self.credentials["client_id"],
"client_key": self.credentials["client_key"],
"grant_type": "client_credentials"
}
response = requests.post(url='https://idp.flutterwave.com/realms/flutterwave/protocol/openid-connect/token', headers=header, data=data)
response_json = response.json()
return response_json
def get_access_token(self):
idx = 0
if self.credentials['access_token'] and self.credentials['expiry'] is None:
self.generate_access_token(self.credentials)
expiry_delta = self.credentials['expiry'] - datetime.now()
if expiry_delta < timedelta(minutes=1):
self.generate_access_token(self.credentials)
return self.credentials['access_token']
<?php
declare(strict_types=1);
namespace Flutterwave;
use Exception;
use Flutterwave\Concerns\Configuration\OAuthConfigurationResolver;
use Flutterwave\Exception\FlutterwaveException;
use Flutterwave\ValueObjects\AccessToken;
use function curl_close;
final class ProfileManager
{
public const TOKEN_EXPIRY_THRESHOLD = 59; //about a minute
public const TOKEN_URL = 'https://keycloak.dev-flutterwave.com/realms/flutterwave/protocol/openid-connect/token';
public static int $requestCount = 0;
private static array $tokens = [];
private OAuthConfigurationResolver $resolver;
public function __construct(OAuthConfigurationResolver $resolver)
{
$this->resolver = $resolver;
}
/**
* @throws Exception
*/
public function addAccount($profile): self
{
if (empty($profile)) {
throw new FlutterwaveException('Profile name cannot be empty');
}
if (isset(self::$tokens[$profile])) {
throw new FlutterwaveException('Profile already exists');
}
$config = $this->resolver->resolveOAuthConfig(['profile' => $profile]);
self::$tokens[$profile] = [
'token' => $this->getToken($config['client_id'], $config['client_secret']),
'client_id' => $config['client_id'],
];
return $this;
}
public function getAccounts(): array
{
return array_keys(self::$tokens);
}
/**
* @throws Exception
*/
public function getAccount($profile): array
{
// $config = $this->resolver->resolveOAuthConfig(['profile' => $profile]);
return self::$tokens[$profile] ?? [];
}
/**
* @throws Exception
*/
public function getAccessToken($profile): AccessToken
{
if (! isset(self::$tokens[$profile])) {
if ($profile !== 'default') {
throw new FlutterwaveException('Credentials related to the '.$profile.' profile not found.');
}
throw new FlutterwaveException('Default Credential are not set. set the environment variables `FLW_CLIENT_ID` and `FLW_CLIENT_SECRET` to use the default credentials or pass a profile name to in the sdk constructor to use a different profile.');
}
$token = self::$tokens[$profile]['token'];
if ($token->isAboutToExpire(self::TOKEN_EXPIRY_THRESHOLD)) {
$config = $this->resolver->resolveOAuthConfig(['profile' => $profile]);
self::$tokens[$profile]['token'] = $this->getToken($config['client_id'], $config['client_secret']);
}
return self::$tokens[$profile]['token'];
}
/**
* @throws Exception
*/
private function getToken($clientId, $clientSecret): AccessToken
{
self::$requestCount++;
$postFields = http_build_query([
'grant_type' => 'client_credentials',
'client_id' => $clientId,
'client_secret' => $clientSecret,
]);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, self::TOKEN_URL);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/x-www-form-urlencoded',
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
throw new FlutterwaveException('Error during OAuth token request: '.curl_error($ch));
}
$responseBody = json_decode($response, true);
curl_close($ch);
if (isset($responseBody['access_token'])) {
return new AccessToken($responseBody['access_token'], $responseBody['expires_in']);
}
throw new FlutterwaveException('Failed to retrieve access token. Response: '.$response);
}
}
Updated about 23 hours ago