When a transaction status changes, ApiPro sends an HTTP callback (webhook) to your specified endpoint.
You must verify the signature of every incoming callback to ensure it was sent by ApiPro and has not been tampered with.
Warning: Skipping signature validation exposes your integration to serious security risks. A third party could replicate a callback request and post arbitrary data to your endpoint.
To enhance protection, you may also restrict incoming requests to our official IP address ranges. Contact your account manager to obtain the current list of IPs.
How It Works
Every callback request from ApiPro includes the following headers used for verification:
Header
Description
Signature
Contains keyId (your API public key), algorithm, list of signed headers, and the signature value
Digest
SHA-256 hash of the request body, Base64-encoded: SHA-256=<base64>
Content-Length
Length of the request body in bytes
Verification Steps
Calculate Digest — compute SHA-256=Base64(SHA256(body)) and compare with the Digest header
Parse Signature header — extract keyId, headers, and signature from the Signature header
Verify keyId — ensure keyId matches your API public key
Build signature payload — concatenate headers listed in headers as "name: value" strings
Compute HMAC-SHA256 — sign the payload with your API secret key and compare with signature
Example HTTP Request
Assume ApiPro sends the following HTTP request to your callback endpoint https://merchant.com/callback:
Tip: The headers field in the Signature header tells you exactly which headers were included in the signature payload. In this example: host, digest, and content-length.
Verification Code Examples
Verify a callback signature using your API Keys:
API Key (Public):f5b0899bd9412f75e5d734fce7cb601a4f096e34695gh9f9ab6f1717196704a9
API Secret (Private):d4479c0af1a913c93fa65a9e82fe7374a74890daaa7a8abb423b4a0e47a405a1
JavaScript
C#
Java
Python
const sha256 = require("crypto-js/sha256");
const hmacSHA256 = require("crypto-js/hmac-sha256");
const base64 = require("crypto-js/enc-base64");
// Your API keys
const apiKey = "f5b0899bd9412f75e5d734fce7cb601a4f096e34695gh9f9ab6f1717196704a9";
const apiSecret = "d4479c0af1a913c93fa65a9e82fe7374a74890daaa7a8abb423b4a0e47a405a1";
function verifyCallback(req, body) {
// Step 1: Verify digest
const expectedDigest = "SHA-256=" + base64.stringify(sha256(body));
if (req.headers["digest"] !== expectedDigest) {
throw new Error("Invalid digest. Expected " + expectedDigest + " got " + req.headers["digest"]);
}
// Step 2: Parse Signature header
let signatureParams = {};
req.headers["signature"].split(", ").forEach((item) => {
const n = item.indexOf("=");
const key = item.substring(0, n);
const value = item.substring(n + 2, item.length - 1);
signatureParams[key] = value;
});
// Step 3: Verify API key
if (signatureParams["keyId"] !== apiKey) {
throw new Error("Invalid API key");
}
// Step 4: Build signature payload
let signaturePayload = "";
signatureParams["headers"].split(" ").forEach((item) => {
const header = req.headers[item.toLowerCase()];
if (!header) {
throw new Error("Undefined header " + item);
}
signaturePayload += item + ": " + header;
});
// Step 5: Verify signature using HMAC-SHA256
const calculatedSignature = base64.stringify(hmacSHA256(signaturePayload, apiSecret));
if (calculatedSignature !== signatureParams["signature"]) {
throw new Error("Invalid signature");
}
console.log("Callback signature verified");
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
public class CallbackVerifier
{
private const string ApiKey = "f5b0899bd9412f75e5d734fce7cb601a4f096e34695gh9f9ab6f1717196704a9";
private const string ApiSecret = "d4479c0af1a913c93fa65a9e82fe7374a74890daaa7a8abb423b4a0e47a405a1";
public static void VerifyCallback(HttpRequest request, string body)
{
// Step 1: Verify digest
string expectedDigest = "SHA-256=" + CalculateSHA256(body);
string receivedDigest = request.Headers["Digest"];
if (expectedDigest != receivedDigest)
{
throw new Exception("Invalid digest");
}
// Step 2: Parse Signature header
var signatureParams = ParseSignatureHeader(request.Headers["Signature"]);
// Step 3: Verify API key
if (signatureParams["keyId"] != ApiKey)
{
throw new Exception("Invalid API key");
}
// Step 4: Build signature payload
var signedHeaders = signatureParams["headers"].Split(' ');
var signaturePayload = string.Join("", signedHeaders.Select(h =>
$"{h}: {request.Headers[h]}"
));
// Step 5: Verify signature
string calculatedSignature = CalculateHMAC(signaturePayload, ApiSecret);
if (calculatedSignature != signatureParams["signature"])
{
throw new Exception("Invalid signature");
}
Console.WriteLine("Callback signature verified");
}
private static Dictionary<string, string> ParseSignatureHeader(string header)
{
var result = new Dictionary<string, string>();
foreach (var item in header.Split(", "))
{
var eqIndex = item.IndexOf('=');
var key = item.Substring(0, eqIndex);
var value = item.Substring(eqIndex + 2, item.Length - eqIndex - 3);
result[key] = value;
}
return result;
}
private static string CalculateSHA256(string input)
{
using (var sha256 = SHA256.Create())
{
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
return Convert.ToBase64String(hash);
}
}
private static string CalculateHMAC(string message, string secret)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
{
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return Convert.ToBase64String(hash);
}
}
}
import hmac
import hashlib
import base64
API_KEY = "f5b0899bd9412f75e5d734fce7cb601a4f096e34695gh9f9ab6f1717196704a9"
API_SECRET = "d4479c0af1a913c93fa65a9e82fe7374a74890daaa7a8abb423b4a0e47a405a1"
def verify_callback(request, body):
"""Verify callback signature from ApiPro"""
# Step 1: Verify digest
expected_digest = "SHA-256=" + base64.b64encode(
hashlib.sha256(body.encode()).digest()
).decode()
received_digest = request.headers.get("Digest")
if expected_digest != received_digest:
raise Exception("Invalid digest")
# Step 2: Parse Signature header
signature_params = parse_signature_header(request.headers.get("Signature"))
# Step 3: Verify API key
if signature_params["keyId"] != API_KEY:
raise Exception("Invalid API key")
# Step 4: Build signature payload
signed_headers = signature_params["headers"].split(" ")
signature_payload = ""
for header in signed_headers:
value = request.headers.get(header)
if not value:
raise Exception(f"Undefined header {header}")
signature_payload += f"{header}: {value}"
# Step 5: Verify signature using HMAC-SHA256
calculated_signature = base64.b64encode(
hmac.new(
API_SECRET.encode(),
signature_payload.encode(),
hashlib.sha256
).digest()
).decode()
if calculated_signature != signature_params["signature"]:
raise Exception("Invalid signature")
print("Callback signature verified")
def parse_signature_header(header):
"""Parse Signature header into dictionary"""
params = {}
for item in header.split(", "):
eq_index = item.index("=")
key = item[:eq_index]
value = item[eq_index + 2 : -1]
params[key] = value
return params
Response Requirements
Your callback endpoint should:
Respond with HTTP 200 OK status code within 10 seconds
Process the callback asynchronously if needed
Return a simple acknowledgment: {"status": "received"}
Retry Policy: If ApiPro doesn't receive a 200 response, it will retry the callback up to 3 times with exponential backoff (1 minute, 5 minutes, 15 minutes).