Skip to main content

Documentation Index

Fetch the complete documentation index at: https://developers.kotanipay.com/llms.txt

Use this file to discover all available pages before exploring further.

When request signing (isSecure mode) is enabled on your integrator account, every request you send to the Kotani Pay API must include three additional headers carrying an HMAC-SHA256 signature. The server rejects any request that is missing these headers, uses a stale timestamp, or replays a nonce it has already seen.
Request signing governs requests from your server to Kotani Pay. It is separate from webhook signature verification, which covers the opposite direction — notifications Kotani Pay sends to your server. See Webhook Notifications for that.

Required Headers

Include all three headers on every request.
HeaderDescription
x-timestampUnix timestamp in seconds (e.g. 1715123456). Must be within ±5 minutes of server time.
x-nonceUUID v4. Must be unique per request — each value is accepted exactly once.
x-signatureHMAC-SHA256 of the signing payload using your API secret, hex-encoded.

Signing Payload

Construct the payload string before computing the signature. POST / PUT / PATCH
${timestamp}.${nonce}.${JSON.stringify(requestBody)}
GET
${timestamp}.${nonce}.${lastPathSegment}
where lastPathSegment is the final segment of the request URL path. For example, a request to /api/v3/wallets/fiat/64a1b2c3d4e5f6a7b8c9d0e2 uses 64a1b2c3d4e5f6a7b8c9d0e2.
Serialize the request body with no extra spaces — compact JSON only. Any whitespace difference between your serialisation and the server’s will cause a signature mismatch and a 401 Unauthorized response.

Example signature value

8b5a21c2e33f3cf33a2e9c92d585cb5a1c304b6f63f91d2f6b0a428cbe9854d1

Postman Pre-request Script

Paste this into the Pre-request Script tab of your Postman collection or individual request. It reads your secret from a Postman environment variable and automatically injects the three signing headers before each request.
// Kotani Pay — Request Signing Pre-request Script
// Add KOTANI_PAY_SIGNATURE to your Postman environment before running.

const secret = pm.environment.get("KOTANI_PAY_SIGNATURE");
if (!secret) {
  console.warn("[KotaniPay] KOTANI_PAY_SIGNATURE is not set in the active environment.");
}

const timestamp = Math.floor(Date.now() / 1000).toString();

// UUID v4 — Postman sandbox does not expose a uuid library
const nonce = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
  const r = (Math.random() * 16) | 0;
  return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
});

const method = pm.request.method;
let payload;

if (method === "GET") {
  const parts = pm.request.url.getPath().split("/");
  const lastSegment = parts[parts.length - 1] || "";
  payload = `${timestamp}.${nonce}.${lastSegment}`;
} else {
  const body = pm.request.body?.raw || "{}";
  payload = `${timestamp}.${nonce}.${body}`;
}

// CryptoJS is available globally in the Postman sandbox
const signature = CryptoJS.HmacSHA256(payload, secret).toString();

pm.request.headers.add({ key: "x-timestamp", value: timestamp });
pm.request.headers.add({ key: "x-nonce",     value: nonce });
pm.request.headers.add({ key: "x-signature", value: signature });

console.log("[KotaniPay] Signed:", { timestamp, nonce, payload });
Setup steps:
  1. Open your collection → Variables tab (or Environments) and add KOTANI_PAY_SIGNATURE with your secret value.
  2. Paste the script into the collection’s Pre-request Script tab so it runs for every request automatically.
  3. Set your request body as raw → JSON. The script reads pm.request.body.raw directly, so the body must already be the final JSON string before the script runs.
Postman uses CryptoJS (not Node’s crypto module) — CryptoJS is available globally with no require() needed. Do not use crypto.createHmac here; it will throw a reference error.

Code Snippets

Node.js

import crypto from "crypto";
import { v4 as uuidv4 } from "uuid";

function signRequest({
  secret,
  body,
  method,
}: {
  secret: string;
  body: Record<string, unknown> | string;
  method: string;
}) {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = uuidv4();
  const payload =
    method === "GET"
      ? `${timestamp}.${nonce}.${body}` // body = last path segment for GETs
      : `${timestamp}.${nonce}.${JSON.stringify(body)}`;

  const signature = crypto
    .createHmac("sha256", secret)
    .update(payload, "utf8")
    .digest("hex");

  return {
    "x-timestamp": timestamp,
    "x-nonce": nonce,
    "x-signature": signature,
  };
}
Usage:
const headers = signRequest({
  secret: process.env.KOTANI_PAY_SIGNATURE!,
  body: { wallet_id: "64a1b2c3d4e5f6a7b8c9d0e2", amount: 1000 },
  method: "POST",
});

await fetch("https://api.kotanipay.com/api/v3/deposits/mobile-money", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.KOTANI_API_KEY}`,
    "Content-Type": "application/json",
    ...headers,
  },
  body: JSON.stringify({ wallet_id: "64a1b2c3d4e5f6a7b8c9d0e2", amount: 1000 }),
});

Python

import hmac, hashlib, time, uuid, json

def sign_request(secret: str, body: dict | str, method: str) -> dict:
    timestamp = str(int(time.time()))
    nonce = str(uuid.uuid4())
    if method.upper() == "GET":
        payload = f"{timestamp}.{nonce}.{body}"
    else:
        payload = f"{timestamp}.{nonce}.{json.dumps(body, separators=(',', ':'))}"
    signature = hmac.new(
        secret.encode(), payload.encode(), hashlib.sha256
    ).hexdigest()
    return {"x-timestamp": timestamp, "x-nonce": nonce, "x-signature": signature}

Security Notes

  • The nonce is single-use. Reusing a nonce that the server has already accepted returns 401 Unauthorized. Generate a fresh UUID v4 for every request.
  • The timestamp window is ±5 minutes. A request whose x-timestamp falls outside that window returns 401 Unauthorized. Ensure your server clock is NTP-synchronised.
  • Compact JSON serialisation is required. Use JSON.stringify without formatting options in Node.js, and json.dumps(..., separators=(',', ':')) in Python. Extra whitespace breaks the signature.
  • Your API secret is shown once. The secret is displayed at key generation time and cannot be retrieved afterwards. Store it in a secrets manager or environment variable — never in source code.
  • Secure mode is permanent. Once request signing is activated for your account it cannot be turned off. Test your signing implementation in the sandbox environment before going live.

Getting Your API Secret

Your API secret is generated alongside your API key in the dashboard.
  1. Log in → SettingsAPI Keys
  2. Click Generate Key and choose Secure mode
  3. Copy both the API key and the API secret immediately — the secret is shown only once
If you have an existing API key without a secure secret, generate a new key with secure mode enabled. See API Keys for full key management instructions.