Signatures
Incognito Payments uses HMAC-SHA256 signatures, keyed with your private key, in two places. Both produce a lowercase hex string. This is the same scheme Tripay uses, so existing Tripay signing code works unchanged.
1. Request signature
Section titled “1. Request signature”Sent in the body of Create transaction as the signature field. It proves
you hold the private key before we create anything.
signature = HMAC_SHA256( merchant_code + merchant_ref + amount )keyed with your private key, whereamountis the integer rendered as a string.
So for merchant M0001, ref ORDER-1001, amount 150000, the signed string is
exactly M0001ORDER-1001150000.
import { createHmac } from "node:crypto";
const signature = createHmac("sha256", privateKey) .update(`${merchantCode}${merchantRef}${amount}`) .digest("hex");
// Or, with the SDK:const sig = payments.signature("ORDER-1001", 150000);$signature = hash_hmac( 'sha256', $merchantCode . $merchantRef . $amount, $privateKey);
// Or, with the SDK:$sig = $payments->signature('ORDER-1001', 150000);SIGNATURE=$(printf '%s' "M0001ORDER-1001150000" \ | openssl dgst -sha256 -hmac "$PRIVATE_KEY" -r | cut -d' ' -f1)If you use an SDK, you usually never compute this by hand — createTransaction
signs the request for you.
2. Callback signature
Section titled “2. Callback signature”Sent by us in the X-Callback-Signature header when we POST a callback to
your server. It proves the callback really came from Incognito Payments.
X-Callback-Signature = HMAC_SHA256( raw_request_body )keyed with your private key.
The signature is computed over the exact bytes of the JSON body we send. Verify it over the raw body you receive — do not re-serialize the parsed JSON first, because key order or whitespace can differ and the signature will not match.
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyCallback(rawBody: string, header: string, privateKey: string) { const expected = createHmac("sha256", privateKey).update(rawBody).digest("hex"); const a = Buffer.from(expected); const b = Buffer.from(header); return a.length === b.length && timingSafeEqual(a, b);}
// Or, with the SDK:payments.verifyCallback(rawBody, req.header("X-Callback-Signature") ?? "");function verifyCallback(string $rawBody, string $header, string $privateKey): bool { $expected = hash_hmac('sha256', $rawBody, $privateKey); return hash_equals($expected, $header);}
// Or, with the SDK:$payments->verifyCallback($rawBody, $request->header('X-Callback-Signature'));Which key signs what?
Section titled “Which key signs what?”The private key that matches the environment of the transaction is used:
- A sandbox transaction (created with the
DEV-key) is signed with your sandbox private key (devsk-…). - A production transaction is signed with your production private key
(
prodsk-…).
If a callback fails to verify, check that you are using the private key for the right environment.