Callback Verification

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

  1. Calculate Digest — compute SHA-256=Base64(SHA256(body)) and compare with the Digest header
  2. Parse Signature header — extract keyId, headers, and signature from the Signature header
  3. Verify keyId — ensure keyId matches your API public key
  4. Build signature payload — concatenate headers listed in headers as "name: value" strings
  5. 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:

POST /callback HTTP/1.1
Host: merchant.com
Content-Type: application/json
Signature: keyId="f5b0899bd9412f75e5d734fce7cb601a4f096e34695gh9f9ab6f1717196704a9", algorithm="HmacSHA256", headers="host digest content-length", signature="k1wrWHTlaan9coI1dRDoY6HMbsoBSyxbsncwyW84jks="
Digest: SHA-256=WxDSD/tnly6hK+scOrP/tacMY2lnwHRmgZnm66gv2gU=
Content-Length: 20

{"some": "variable"}
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");
}

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).