Fonbnk Docs
  • Intro
  • Integration Guide
  • On-ramp
    • How it works
    • URL Parameters
    • Webhook
  • Off-ramp
    • How it works
    • URL Parameters
    • Webhook
  • Reference
    • Servers
    • Signing requests
    • Endpoints
      • On-ramp
        • Assets
        • Order
        • Orders
        • Price
        • Providers
        • Limits
      • Off-ramp
        • Order
        • Orders
        • Best offer
        • Limits
        • Countries
        • Wallets
        • Validate fields
        • Create order
        • Confirm order
      • Util
        • Check address
        • Assets
      • Kyc
        • State
        • Submit
    • Specification
Powered by GitBook
On this page
  • Request Authentication​
  • How to get the signature of the request?​
  • Request examples
  1. Reference

Signing requests

PreviousServersNextEndpoints

Last updated 7 months ago

Request Authentication

All requests should be signed using a HMAC256 algorithm and provided clientId and clientSecret.

How to get the signature of the request?

  1. Generate a timestamp (Epoch Unix Timestamp) in milliseconds

  2. Concatenate the timestamp and the endpoint that is called {timestamp}:{endpoint}

  3. Decode the base64 encoded clientSecret

  4. Compute the SHA256 hash of the concatenated string. Use decoded clientSecret as a key. Convert the result to base64

  5. Add the clientId, signature, and timestamp to HTTP headers

The following pseudocode example demonstrates and explains how to sign a request

timestamp = CurrentTimestamp();
stringToSign = timestamp + ":" + endpoint;
signature = Base64 ( HMAC-SHA256 ( Base64-Decode ( clientSecret ), UTF8 ( concatenatedString ) ) );

Request examples

The following examples send HTTP request to get price on-ramp API endpoint:

import crypto from 'crypto';
const BASE_URL = 'https://aten.fonbnk-services.com';
const ENDPOINT = '/api/pay-widget-merchant/price';
const CLIENT_ID = '';
const CLIENT_SECRET = '';

const generateSignature = ({
  clientSecret,
  timestamp,
  endpoint,
}: {
  clientSecret: string;
  timestamp: string;
  endpoint: string;
}) => {
  let hmac = crypto.createHmac('sha256', Buffer.from(clientSecret, 'base64'));
  let stringToSign = `${timestamp}:${endpoint}`;
  hmac.update(stringToSign);
  return hmac.digest('base64');
};

const main = async () => {
  const timestamp = new Date().getTime();
  const queryParams = new URLSearchParams({
    country: 'NG',
    amount: '10',
    currency: 'usdc',
    network: 'CELO',
    asset: 'CUSD',
    provider: 'bank_transfer',
  });
  const endpoint = `${ENDPOINT}?${queryParams.toString()}`;
  const signature = generateSignature({
    clientSecret: CLIENT_SECRET,
    timestamp: timestamp.toString(),
    endpoint,
  });
  const headers = {
    'Content-Type': 'application/json',
    'x-client-id': CLIENT_ID,
    'x-timestamp': timestamp.toString(),
    'x-signature': signature,
  };
  const response = await fetch(`${BASE_URL}${endpoint}`, {
    method: 'GET',
    headers,
  });
  const data = await response.json();
  console.log(JSON.stringify(data, null, 2));
};

main().catch(console.error);
import hmac
import base64
import time
import requests
from urllib.parse import urlencode

BASE_URL = 'https://aten.fonbnk-services.com'
ENDPOINT = '/api/pay-widget-merchant/price'
CLIENT_ID = ''
CLIENT_SECRET = ''

def pad_base64(base64_string):
    return base64_string + '=' * (-len(base64_string) % 4)

def generate_signature(client_secret, timestamp, endpoint):
    client_secret_padded = pad_base64(client_secret)
    hmac_obj = hmac.new(base64.b64decode(client_secret_padded), f'{timestamp}:{endpoint}'.encode('utf-8'), 'sha256')
    return base64.b64encode(hmac_obj.digest()).decode('utf-8')

def main():
    timestamp = str(int(time.time() * 1000))
    query_params = {
        'country': 'NG',
        'amount': '10',
        'currency': 'usdc',
        'network': 'CELO',
        'asset': 'CUSD',
        'provider': 'bank_transfer',
    }
    endpoint = f"{ENDPOINT}?{urlencode(query_params)}"
    signature = generate_signature(CLIENT_SECRET, timestamp, endpoint)
    headers = {
        'Content-Type': 'application/json',
        'x-client-id': CLIENT_ID,
        'x-timestamp': timestamp,
        'x-signature': signature,
    }
    response = requests.get(f"{BASE_URL}{endpoint}", headers=headers)
    data = response.json()
    print(data)

if __name__ == "__main__":
    main()
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	BASE_URL      = "https://aten.fonbnk-services.com"
	ENDPOINT      = "/api/pay-widget-merchant/price"
	CLIENT_ID     = ""
	CLIENT_SECRET = ""
)

func padBase64(base64String string) string {
	return base64String + strings.Repeat("=", (4-len(base64String)%4)%4)
}

func generateSignature(clientSecret, timestamp, endpoint string) (string, error) {
	clientSecretPadded := padBase64(clientSecret)
	decodedSecret, err := base64.StdEncoding.DecodeString(clientSecretPadded)
	if err != nil {
		return "", err
	}
	message := fmt.Sprintf("%s:%s", timestamp, endpoint)
	h := hmac.New(sha256.New, decodedSecret)
	h.Write([]byte(message))
	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
	return signature, nil
}

func main() {
	timestamp := fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Millisecond))
	queryParams := url.Values{
		"country":  {"NG"},
		"amount":   {"10"},
		"currency": {"usdc"},
		"network":  {"CELO"},
		"asset":    {"CUSD"},
		"provider": {"bank_transfer"},
	}
	endpoint := fmt.Sprintf("%s?%s", ENDPOINT, queryParams.Encode())
	signature, err := generateSignature(CLIENT_SECRET, timestamp, endpoint)
	if err != nil {
		fmt.Println("Error generating signature:", err)
		return
	}

	client := &http.Client{}
	req, err := http.NewRequest("GET", BASE_URL+endpoint, nil)
	if err != nil {
		fmt.Println("Error creating request:", err)
		return
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("x-client-id", CLIENT_ID)
	req.Header.Set("x-timestamp", timestamp)
	req.Header.Set("x-signature", signature)

	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("Error making request:", err)
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Error reading response body:", err)
		return
	}

	var data map[string]interface{}
	if err := json.Unmarshal(body, &data); err != nil {
		fmt.Println("Error unmarshalling response:", err)
		return
	}

	fmt.Println(data)
}
<?php

define('BASE_URL', 'https://aten.fonbnk-services.com');
define('ENDPOINT', '/api/pay-widget-merchant/price');
define('CLIENT_ID', '');
define('CLIENT_SECRET', '');

function pad_base64($base64_string) {
    return $base64_string . str_repeat('=', (4 - strlen($base64_string) % 4) % 4);
}

function generate_signature($client_secret, $timestamp, $endpoint) {
    $client_secret_padded = pad_base64($client_secret);
    $hmac = hash_hmac('sha256', "$timestamp:$endpoint", base64_decode($client_secret_padded), true);
    return base64_encode($hmac);
}

function main() {
    $timestamp = (string) round(microtime(true) * 1000);
    $query_params = [
        'country' => 'NG',
        'amount' => '10',
        'currency' => 'usdc',
        'network' => 'CELO',
        'asset' => 'CUSD',
        'provider' => 'bank_transfer',
    ];
    $endpoint = ENDPOINT . '?' . http_build_query($query_params);
    $signature = generate_signature(CLIENT_SECRET, $timestamp, $endpoint);
    $headers = [
        'Content-Type: application/json',
        'x-client-id: ' . CLIENT_ID,
        'x-timestamp: ' . $timestamp,
        'x-signature: ' . $signature,
    ];

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, BASE_URL . $endpoint);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);
    print_r($data);
}

main();
?>
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {
    private static final String BASE_URL = "https://aten.fonbnk-services.com";
    private static final String ENDPOINT = "/api/pay-widget-merchant/price";
    private static final String CLIENT_ID = "";
    private static final String CLIENT_SECRET = "";

    public static void main(String[] args) throws Exception {
        long timestamp = System.currentTimeMillis();
        Map<String, String> queryParams = new HashMap<>();
        queryParams.put("country", "NG");
        queryParams.put("amount", "10");
        queryParams.put("currency", "usdc");
        queryParams.put("network", "CELO");
        queryParams.put("asset", "CUSD");
        queryParams.put("provider", "bank_transfer");

        String endpoint = ENDPOINT + "?" + getQuery(queryParams);
        String signature = generateSignature(CLIENT_SECRET, String.valueOf(timestamp), endpoint);

        URL url = new URL(BASE_URL + endpoint);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setRequestProperty("x-client-id", CLIENT_ID);
        connection.setRequestProperty("x-timestamp", String.valueOf(timestamp));
        connection.setRequestProperty("x-signature", signature);

        Scanner scanner = new Scanner(connection.getInputStream());
        String response = scanner.useDelimiter("\\A").next();
        System.out.println(response);
        scanner.close();
    }

    private static String padBase64(String base64String) {
        return base64String + "=".repeat((4 - base64String.length() % 4) % 4);
    }

    private static String generateSignature(String clientSecret, String timestamp, String endpoint) throws Exception {
        String clientSecretPadded = padBase64(clientSecret);
        SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(clientSecretPadded), "HmacSHA256");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(secretKeySpec);
        String data = timestamp + ":" + endpoint;
        byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(hmacBytes);
    }

    private static String getQuery(Map<String, String> params) throws Exception {
        StringBuilder result = new StringBuilder();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (result.length() > 0) {
                result.append("&");
            }
            result.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
            result.append("=");
            result.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
        }
        return result.toString();
    }
}
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;

void main() async {
  const String BASE_URL = "https://aten.fonbnk-services.com";
  const String ENDPOINT = "/api/pay-widget-merchant/price";
  const String CLIENT_ID = "";
  const String CLIENT_SECRET = "";

  // Get the current timestamp in milliseconds
  int timestamp = DateTime.now().millisecondsSinceEpoch;

  // Create query parameters
  Map<String, String> queryParams = {
    "country": "NG",
    "amount": "10",
    "currency": "usdc",
    "network": "CELO",
    "asset": "CUSD",
    "provider": "bank_transfer",
  };

  // Generate the query string
  String queryString = getQuery(queryParams);

  // Create the endpoint with query parameters
  String endpoint = ENDPOINT + "?" + queryString;

  // Generate the signature
  String signature = generateSignature(CLIENT_SECRET, timestamp.toString(), endpoint);

  // Build the URL
  String url = BASE_URL + endpoint;

  // Set up the HTTP GET request
  var headers = {
    "Content-Type": "application/json",
    "x-client-id": CLIENT_ID,
    "x-timestamp": timestamp.toString(),
    "x-signature": signature,
  };

  // Send the GET request
  var response = await http.get(Uri.parse(url), headers: headers);

  // Print the response body
  print(response.body);
}

String getQuery(Map<String, String> params) {
  return params.entries
      .map((entry) =>
  Uri.encodeQueryComponent(entry.key) + "=" + Uri.encodeQueryComponent(entry.value))
      .join("&");
}

String generateSignature(String clientSecret, String timestamp, String endpoint) {
  // Use the custom lenient Base64 decoder
  List<int> secretKey = lenientBase64Decode(clientSecret);

  Hmac hmac = Hmac(sha256, secretKey);
  String data = '$timestamp:$endpoint';
  Digest digest = hmac.convert(utf8.encode(data));

  // Encode the signature using Base64
  String signature = base64Encode(digest.bytes);
  return signature;
}

List<int> lenientBase64Decode(String input) {
  // Base64 index table
  const String base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

  // Remove all characters that are not in the Base64 alphabet
  String sanitizedInput = input.replaceAll(RegExp(r'[^A-Za-z0-9+/]'), '');

  // Map each character to its Base64 index
  List<int> buffer = [];
  int bits = 0;
  int bitsCount = 0;

  for (int i = 0; i < sanitizedInput.length; i++) {
    int val = base64Chars.indexOf(sanitizedInput[i]);
    if (val < 0) {
      // Skip invalid characters
      continue;
    }
    bits = (bits << 6) | val;
    bitsCount += 6;
    if (bitsCount >= 8) {
      bitsCount -= 8;
      int byte = (bits >> bitsCount) & 0xFF;
      buffer.add(byte);
    }
  }

  return buffer;
}
Mix.install([
  {:httpoison, "~> 1.8"},
  {:jason, "~> 1.4"}
])

defmodule FonbnkClient do
  @moduledoc """
  A client for interacting with the Fonbnk API.
  """

  @base_url "https://aten.fonbnk-services.com"
  @endpoint "/api/pay-widget-merchant/price"
  @client_id ""
  @client_secret ""

  def pad_base64(base64_string) do
    pad_length = Integer.mod(-String.length(base64_string), 4)
    base64_string <> String.duplicate("=", pad_length)
  end

  def generate_signature(client_secret, timestamp, endpoint) do
    client_secret_padded = pad_base64(client_secret)
    {:ok, client_secret_decoded} = Base.decode64(client_secret_padded)
    message = "#{timestamp}:#{endpoint}"
    hmac = :crypto.mac(:hmac, :sha256, client_secret_decoded, message)
    Base.encode64(hmac)
  end

  def main do
    timestamp = :os.system_time(:millisecond) |> Integer.to_string()
    query_params = %{
      "country" => "NG",
      "amount" => "10",
      "currency" => "usdc",
      "network" => "CELO",
      "asset" => "CUSD",
      "provider" => "bank_transfer"
    }

    encoded_query = URI.encode_query(query_params)
    endpoint = @endpoint <> "?" <> encoded_query
    signature = generate_signature(@client_secret, timestamp, endpoint)

    headers = [
      {"Content-Type", "application/json"},
      {"x-client-id", @client_id},
      {"x-timestamp", timestamp},
      {"x-signature", signature}
    ]

    url = @base_url <> endpoint

    case HTTPoison.get(url, headers) do
      {:ok, %HTTPoison.Response{body: body, status_code: code}} when code in 200..299 ->
        data = Jason.decode!(body)
        IO.inspect(data)

      {:ok, %HTTPoison.Response{body: body, status_code: code}} ->
        IO.puts("HTTP Error #{code}: #{body}")

      {:error, %HTTPoison.Error{reason: reason}} ->
        IO.puts("Request Error: #{inspect(reason)}")
    end
  end
end

FonbnkClient.main()
​
​