# Signing requests

### Request Authentication[​](https://docs.fonbnk.com/docs/pay-widget/merchant-api#request-authentication) <a href="#request-authentication" id="request-authentication"></a>

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

### How to get the signature of the request?[​](https://docs.fonbnk.com/docs/pay-widget/merchant-api#how-to-get-the-signature-of-the-request) <a href="#how-to-get-the-signature-of-the-request" id="how-to-get-the-signature-of-the-request"></a>

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

{% code overflow="wrap" %}

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

{% endcode %}

## Request examples <a href="#request-examples" id="request-examples"></a>

The following examples send HTTP request to [Get order limits](/server-to-server/api-endpoints/get-order-limits.md) API endpoint:

{% tabs %}
{% tab title="Typescript" %}
{% code overflow="wrap" %}

```typescript
import crypto from 'crypto';
const BASE_URL = 'https://api.fonbnk.com';
const ENDPOINT = '/api/v2/order-limits;
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({
    depositPaymentChannel: 'bank',
    depositCurrencyType: 'fiat',
    depositCurrencyCode: 'NGN',
    depositCountryIsoCode: 'NG',
    payoutPaymentChannel: 'crypto',
    payoutCurrencyType: 'crypto',
    payoutCurrencyCode: 'CELO_USDT',
  });
  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);

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code overflow="wrap" %}

```python
import hmac
import base64
import time
import requests
from urllib.parse import urlencode

BASE_URL = 'https://api.fonbnk.com'
ENDPOINT = '/api/v2/order-limits'
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 = {
        'depositPaymentChannel': 'bank',
        'depositCurrencyType': 'fiat',
        'depositCurrencyCode': 'NGN',
        'depositCountryIsoCode': 'NG',
        'payoutPaymentChannel': 'crypto',
        'payoutCurrencyType': 'crypto',
        'payoutCurrencyCode': 'CELO_USDT',
    }
    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()

```

{% endcode %}
{% endtab %}

{% tab title="GO" %}
{% code overflow="wrap" %}

```go
package main

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

const (
	BASE_URL      = "https://api.fonbnk.com"
	ENDPOINT      = "/api/v2/order-limits"
	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{
		"depositPaymentChannel": {"bank"},
    "depositCurrencyType": {"fiat"},
    "depositCurrencyCode": {"NGN"},
    "depositCountryIsoCode": {"NG"},
    "payoutPaymentChannel": {"crypto"},
    "payoutCurrencyType": {"crypto"},
    "payoutCurrencyCode": {"CELO_USDT"},
	}
	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)
}

```

{% endcode %}
{% endtab %}

{% tab title="PHP" %}
{% code overflow="wrap" %}

```php
<?php

define('BASE_URL', 'https://api.fonbnk.com');
define('ENDPOINT', '/api/v2/order-limits');
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 = [
        'depositPaymentChannel' => 'bank',
        'depositCurrencyType' => 'fiat',
        'depositCurrencyCode' => 'NGN',
        'depositCountryIsoCode' => 'NG',
        'payoutPaymentChannel' => 'crypto',
        'payoutCurrencyType' => 'crypto',
        'payoutCurrencyCode' => 'CELO_USDT',
    ];
    $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();
?>
```

{% endcode %}
{% endtab %}

{% tab title="Java" %}

```java
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://api.fonbnk.com";
    private static final String ENDPOINT = "/api/v2/order-limits";
    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("depositPaymentChannel", "bank");
        queryParams.put("depositCurrencyType", "fiat");
        queryParams.put("depositCurrencyCode", "NGN");
        queryParams.put("depositCountryIsoCode", "NG");
        queryParams.put("payoutPaymentChannel", "crypto");
        queryParams.put("payoutCurrencyType", "crypto");
        queryParams.put("payoutCurrencyCode", "CELO_USDT");
        
        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();
    }
}
```

{% endtab %}

{% tab title="Dart" %}

```dart
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;

void main() async {
  const String BASE_URL = "https://api.fonbnk.com";
  const String ENDPOINT = "/api/v2/order-limits";
  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 = {
     'depositPaymentChannel': 'bank',
     'depositCurrencyType': 'fiat',
     'depositCurrencyCode': 'NGN',
     'depositCountryIsoCode': 'NG',
     'payoutPaymentChannel': 'crypto',
     'payoutCurrencyType': 'crypto',
     'payoutCurrencyCode': 'CELO_USDT',
  };

  // 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;
}
```

{% endtab %}

{% tab title="Elixir" %}

```elixir
Mix.install([
  {:httpoison, "~> 1.8"},
  {:jason, "~> 1.4"}
])

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

  @base_url "https://api.fonbnk.com"
  @endpoint "/api/v2/order-limits"
  @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 = %{
        'depositPaymentChannel' => 'bank',
        'depositCurrencyType' => 'fiat',
        'depositCurrencyCode' => 'NGN',
        'depositCountryIsoCode' => 'NG',
        'payoutPaymentChannel' => 'crypto',
        'payoutCurrencyType' => 'crypto',
        'payoutCurrencyCode' => 'CELO_USDT',
    }

    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()
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.fonbnk.com/server-to-server/signing-requests.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
