Gafiapay API
Generate and manage virtual accounts for your customers, receive payment notifications, and refund payments, from your own server.
Gafiapay gives each of your customers a 10-digit virtual account number. When a customer transfers money to that number, the payment is credited to your Gafiapay balance and your server receives a webhook. The API is REST over HTTPS, takes and returns JSON, and every request is signed with your secret key.
Only verified businesses can use the API. Each account is tied to a customer's name and email, and to their BVN, NIN or CAC number.
Quickstart
-
Create and verify your business
Sign up for a Gafiapay business account and complete KYC. Your verification status must be
completedbefore the API accepts your requests. -
Get your API keys
Generate an API key and a secret key in the dashboard. See API key setup.
-
Sign your requests
Send
x-api-key,x-timestampand an HMAC SHA-256x-signaturewith every call. See Authentication. -
Generate your first account
Call
POST /account/generatewith your customer's details: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", "bvn": "22345678901" }'const crypto = require('crypto'); const body = JSON.stringify({ name: 'John Doe', email: 'john@example.com', bvn: '22345678901' }); 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", "bvn": "22345678901" }, 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', 'bvn' => '22345678901' ], 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', bvn: '22345678901' }.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\",\"bvn\":\"22345678901\"}"; 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());
Base URL
All endpoints are relative to this URL:
https://api.gafiapay.com/api/v1/externalEndpoints
Responses
Every response is JSON with a status field. Successful calls return success and put the result in data:
{
"status": "success",
"data": {
// Response data specific to the endpoint
}
}
Failed calls return fail for a client error (4xx) or error for a server error (5xx), with a readable message. See authentication errors for the most common ones.
Best practices
- Keep keys on your server. Never put your API or secret key in client-side code or a public repository. Store them in environment variables.
- Check
statuson every response. Retry429and5xxerrors with backoff. Before retrying a refund, check its status so you don't refund twice. - Paginate lists. Use
pageandlimitwhen listing accounts or refunds. - Verify your business first. Requests from businesses that aren't verified return
401.
Download the integration guide for AI agents. It's one Markdown file with the whole API and the rules agents most often get wrong. Give it to Claude Code, Cursor or Copilot along with the features you want.