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);
    }
}