Authentication
Every request carries your API key, the current timestamp and an HMAC SHA-256 signature made with your secret key.
The API only accepts requests once your business has passed verification in the dashboard. Until then, every request returns 401 Business not verified.
Required headers
Send these headers with every request. GET requests have no body, so they don't need x-signature.
x-api-key string required x-signature string required with a body x-timestamp string required 1678901234567. It must be within 5 minutes of the server's time.Signing a request
Build the string to sign by joining the request body, serialized as compact JSON (no spaces), and the timestamp. Then compute an HMAC SHA-256 of that string with your secret key and send the hex digest as x-signature.
signature = HMAC_SHA256(secret_key, JSON.stringify(body) + timestamp)const crypto = require('crypto');
function generateSignature(requestBody, timestamp, secretKey) {
const signatureString = `${JSON.stringify(requestBody)}${timestamp}`;
return crypto
.createHmac('sha256', secretKey)
.update(signatureString)
.digest('hex');
}
// Example usage
const requestBody = {
name: "John Doe",
email: "john@example.com"
};
const timestamp = Date.now();
const signature = generateSignature(requestBody, timestamp, 'your_secret_key');
// Headers for the request
const headers = {
'x-api-key': 'your_api_key',
'x-signature': signature,
'x-timestamp': timestamp,
'Content-Type': 'application/json'
};import hmac
import hashlib
import json
import time
def generate_signature(request_body, timestamp, secret_key):
signature_string = json.dumps(request_body, separators=(",", ":")) + str(timestamp) # compact, no spaces
return hmac.new(
secret_key.encode('utf-8'),
signature_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Example usage
request_body = {
"name": "John Doe",
"email": "john@example.com"
}
timestamp = int(time.time() * 1000)
signature = generate_signature(request_body, timestamp, "your_secret_key")
# Headers for the request
headers = {
'x-api-key': 'your_api_key',
'x-signature': signature,
'x-timestamp': str(timestamp),
'Content-Type': 'application/json'
}<?php
function generateSignature($requestBody, $timestamp, $secretKey) {
$signatureString = json_encode($requestBody, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . $timestamp;
return hash_hmac('sha256', $signatureString, $secretKey);
}
// Example usage
$requestBody = [
'name' => 'John Doe',
'email' => 'john@example.com'
];
$timestamp = round(microtime(true) * 1000);
$signature = generateSignature($requestBody, $timestamp, 'your_secret_key');
// Headers for the request
$headers = [
'x-api-key: your_api_key',
'x-signature: ' . $signature,
'x-timestamp: ' . $timestamp,
'Content-Type: application/json'
];require 'openssl'
require 'json'
def generate_signature(request_body, timestamp, secret_key)
signature_string = request_body.to_json + timestamp.to_s
OpenSSL::HMAC.hexdigest('sha256', secret_key, signature_string)
end
# Example usage
request_body = {
name: 'John Doe',
email: 'john@example.com'
}
timestamp = (Time.now.to_f * 1000).to_i
signature = generate_signature(request_body, timestamp, 'your_secret_key')
# Headers for the request
headers = {
'x-api-key' => 'your_api_key',
'x-signature' => signature,
'x-timestamp' => timestamp.to_s,
'Content-Type' => 'application/json'
}import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class SignatureGenerator {
public static String generateSignature(Object requestBody, long timestamp, String secretKey)
throws NoSuchAlgorithmException, InvalidKeyException {
ObjectMapper mapper = new ObjectMapper();
String signatureString = mapper.writeValueAsString(requestBody) + timestamp;
Mac sha256Hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(
secretKey.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
sha256Hmac.init(secretKeySpec);
byte[] signedBytes = sha256Hmac.doFinal(
signatureString.getBytes(StandardCharsets.UTF_8)
);
return bytesToHex(signedBytes);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
// Example usage
public static void main(String[] args) throws Exception {
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("name", "John Doe");
requestBody.put("email", "john@example.com");
long timestamp = System.currentTimeMillis();
String signature = generateSignature(requestBody, timestamp, "your_secret_key");
// Headers for the request
Map<String, String> headers = new HashMap<>();
headers.put("x-api-key", "your_api_key");
headers.put("x-signature", signature);
headers.put("x-timestamp", String.valueOf(timestamp));
headers.put("Content-Type", "application/json");
}
}Serialize the body once and send that same string. If you re-serialize it with different spacing or key order, the signature won't match.
Example request
curl -X POST 'https://api.gafiapay.com/api/v1/external/account/generate' \
-H 'Content-Type: application/json' \
-H 'x-api-key: your_api_key' \
-H 'x-timestamp: 1678901234567' \
-H 'x-signature: generated_signature' \
-d '{
"name": "John Doe",
"email": "john@example.com"
}'const crypto = require('crypto');
const body = JSON.stringify({
name: 'John Doe',
email: 'john@example.com'
});
const timestamp = Date.now().toString();
const signature = crypto
.createHmac('sha256', process.env.GAFIAPAY_SECRET_KEY)
.update(body + timestamp)
.digest('hex');
const res = await fetch('https://api.gafiapay.com/api/v1/external/account/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.GAFIAPAY_API_KEY,
'x-timestamp': timestamp,
'x-signature': signature,
},
body, // send the exact string you signed
});
const data = await res.json();import hashlib, hmac, json, os, time, requests
body = json.dumps({
"name": "John Doe",
"email": "john@example.com"
}, separators=(",", ":")) # compact, no spaces
timestamp = str(int(time.time() * 1000))
signature = hmac.new(
os.environ["GAFIAPAY_SECRET_KEY"].encode(),
(body + timestamp).encode(),
hashlib.sha256,
).hexdigest()
res = requests.post(
"https://api.gafiapay.com/api/v1/external/account/generate",
data=body, # data=, not json=, so the signed string is sent as is
headers={
"Content-Type": "application/json",
"x-api-key": os.environ["GAFIAPAY_API_KEY"],
"x-timestamp": timestamp,
"x-signature": signature,
},
)
data = res.json()<?php
$body = json_encode([
'name' => 'John Doe',
'email' => 'john@example.com'
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$timestamp = (string) round(microtime(true) * 1000);
$signature = hash_hmac('sha256', $body . $timestamp, getenv('GAFIAPAY_SECRET_KEY'));
$ch = curl_init('https://api.gafiapay.com/api/v1/external/account/generate');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('GAFIAPAY_API_KEY'),
'x-timestamp: ' . $timestamp,
'x-signature: ' . $signature,
],
]);
$data = json_decode(curl_exec($ch), true);require 'net/http'
require 'openssl'
require 'json'
body = {
name: 'John Doe',
email: 'john@example.com'
}.to_json
timestamp = (Time.now.to_f * 1000).to_i.to_s
signature = OpenSSL::HMAC.hexdigest('sha256', ENV['GAFIAPAY_SECRET_KEY'], body + timestamp)
uri = URI('https://api.gafiapay.com/api/v1/external/account/generate')
req = Net::HTTP::Post.new(uri)
req['Content-Type'] = 'application/json'
req['x-api-key'] = ENV['GAFIAPAY_API_KEY']
req['x-timestamp'] = timestamp
req['x-signature'] = signature
req.body = body
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)import java.net.URI;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
// Compact JSON, sent exactly as signed
String body = "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}";
String timestamp = String.valueOf(System.currentTimeMillis());
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(System.getenv("GAFIAPAY_SECRET_KEY").getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String signature = HexFormat.of().formatHex(mac.doFinal((body + timestamp).getBytes(StandardCharsets.UTF_8)));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.gafiapay.com/api/v1/external/account/generate"))
.header("Content-Type", "application/json")
.header("x-api-key", System.getenv("GAFIAPAY_API_KEY"))
.header("x-timestamp", timestamp)
.header("x-signature", signature)
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());What the API checks
A request is rejected unless all of these pass:
- API key. The key must be valid and active. Inactive or revoked keys are rejected.
- Business verification. Your business must be verified. Businesses still in review, rejected, or that haven't submitted verification are rejected.
- Timestamp. It must be within 5 minutes of the server's time, which prevents replay attacks.
- Signature. It must match the request body and timestamp. Changing anything in the request invalidates it.
Errors
Authentication failures return 401 Unauthorized with one of these messages:
| Message | Cause |
|---|---|
Missing required header: x-api-key | A required header is missing. The message names it. |
Invalid API key | The key doesn't exist, or it's inactive or revoked. |
Business not verified | Your business hasn't passed verification yet. |
Request timestamp expired | x-timestamp is more than 5 minutes from the server's time. |
Invalid signature | The signature doesn't match the body and timestamp. |
{
"status": "fail",
"message": "Invalid signature"
}Response format
Successful responses use this shape:
{
"status": "success",
"data": {
// Response data specific to the endpoint
}
}Error responses use this shape. status is fail for a 4xx error and error for a 5xx error. Show or log message.
{
"status": "fail",
"message": "Error description"
}