Important Note
⚠️ Important: All Requests Must Be Signed
To ensure request authenticity and integrity, all requests sent from your backend to COUNT's API must be signed using your client secret. This signature helps prevent tampering, replay attacks, and unauthorized access.
We strongly recommend using a reusable Axios instance with request signing built in, as shown below.
🔐 Signature Requirements
Each request must include three critical headers:
x-client-id
should contain your unique client identifier, which you’ll find in your COUNT dashboard.x-timestamp
must be the current UNIX timestamp in seconds when the request is made.x-signature
is the computed HMAC SHA-256 signature based on the request details and your client secret.
The signature is generated using a combination of the HTTP method, the request path (excluding domain), the timestamp, and the hashed request body (for POST
, PUT
, or PATCH
methods). These are combined into a single string, hashed using your client secret, and attached to the request.
Below is the recommended setup using a reusable Axios client to handle signing automatically:
const axios = require("axios");
const crypto = require("crypto");
// Create an Axios instance with base URL and static x-client-id header
const countClient = axios.create({
baseURL: process.env.COUNT_PARTNER_API_ENDPOINT,
headers: {
"x-client-id": process.env.COUNT_CLIENT_ID,
},
});
// Attach the interceptor with a reusable signing function
countClient.interceptors.request.use(signRequest, (error) => Promise.reject(error));
// Function that handles signing the request
function signRequest(config) {
const method = (config.method || "GET").toUpperCase();
const url = config.url || "/";
const urlPath = new URL(url, config.baseURL).pathname;
const timestamp = Math.floor(Date.now() / 1000).toString();
const clientSecret = process.env.COUNT_CLIENT_SECRET;
const body = config.data || {}; const signature = generateSignature({
method,
path: urlPath,
timestamp,
body,
clientSecret,
}); config.headers["x-timestamp"] = timestamp;
config.headers["x-signature"] = signature; return config;
}
// Hash the request body using SHA-256
function hashBody(body) {
return body && Object.keys(body).length > 0
? crypto.createHash("sha256").update(JSON.stringify(body)).digest("hex")
: "";
}
// Build the HMAC base string in the format: METHOD:/path:timestamp:bodyHash
function buildHmacBaseString(method, path, timestamp, bodyHash = "") {
return ${method}:${path}:${timestamp}:${bodyHash};
}
// Generate the HMAC-SHA256 signature
function generateSignature({ method, path, timestamp, body, clientSecret }) {
const bodyHash = ["POST", "PUT", "PATCH"].includes(method)
? hashBody(body)
: ""; const baseString = buildHmacBaseString(method, path, timestamp, bodyHash); return crypto
.createHmac("sha256", clientSecret)
.update(baseString)
.digest("hex");
}
clientSecret
is never exposed on the client side. Requests with invalid or expired signatures will be rejected by COUNT’s API.Updated on: 08/10/2025
Thank you!