Last updated
Authentication#
Sign partner requests consistently, protect the API secret, and understand scopes and request tracing.
Required headers#
The supplied partner authentication design specifies three headers generated from the issued API key and secret. Use the assigned environment's credentials on the server that owns the integration.
| Header | Description |
|---|---|
x-api-key | Issued public API key; examples use pk_sandbox_… and pk_live_…. |
x-timestamp | Current Unix timestamp in milliseconds, represented as a decimal string. |
x-signature | Hex-encoded HMAC-SHA256 of the documented signing input. |
Content-Type | application/json when sending a JSON request body. |
Computing the signature#
The signing input is timestamp + "." + method + "." + path + "." + rawBody. The method is uppercase, the path includes the version prefix, and rawBody is the exact JSON string sent over the connection. Use an empty string when a request has no body.
signature = HMAC-SHA256(secret, timestamp + "." + method + "." + path + "." + rawBody)import { createHmac } from "node:crypto";
export function signRequest({ secret, method, path, rawBody = "", timestamp }) {
if (!secret || !path.startsWith("/")) throw new Error("Invalid signing configuration");
const input = `${timestamp}.${method.toUpperCase()}.${path}.${rawBody}`;
return createHmac("sha256", secret).update(input, "utf8").digest("hex");
}
const rawBody = JSON.stringify({ amount: "2500.00", currency: "NGN" });
const timestamp = Date.now().toString();
const signature = signRequest({
secret: process.env.KLEMPAY_PARTNER_API_SECRET,
method: "POST",
path: "/v1/transfers",
rawBody,
timestamp,
});Keep the signed path identical to the destination path. Do not sign an entire URL unless the endpoint contract explicitly requires it. Confirm how query strings, encoded characters, and trailing slashes are handled by the deployed controller before implementing requests that depend on them.
The design describes a five-minute timestamp window. Keep server clocks synchronised and generate the timestamp immediately before sending. Confirm the deployed tolerance with the integration team; a retry requires a fresh timestamp and signature while retaining the original operation reference.
Your secret#
The API secret should be treated as a credential capable of authorising sensitive operations. Store it in a secret manager, load it only into the server process, and restrict access to the services that actually send requests.
- Never include the secret in browser code, source control, analytics, logs, or a support ticket.
- Redact authorization values and API credential headers in diagnostics.
- Rotate an exposed secret immediately and revoke the compromised key through the approved process.
- Keep sandbox and live credentials isolated by environment.
- If the secret is no longer available, request replacement credentials instead of attempting to retrieve it from a client application.
Scopes#
An issued key has a specific set of permissions. A valid signature does not grant access to endpoints outside those permissions. The following names come from the supplied authentication design; the actual issued scope catalogue remains authoritative.
| Scope | Grants |
|---|---|
accounts:read | Reading account and balance information. |
accounts:write | Opening individual and corporate accounts. |
transfer:read | Checking transfer status. |
transfer:write | Initiating transfers. |
liquidity:read | Checking prefund status and balances. |
liquidity:write | Registering prefund operations. |
fixed-deposit:write | Placing a fixed deposit where the product is enabled. |
loan:write | Initiating an enabled loan or overdraft workflow. |
webhooks:manage | Registering and managing webhook destinations. |
For a 403, inspect the backend message and confirm the requested endpoint, tenant, and assigned scope. Repeating the same request does not change permissions. Ask the integration team to verify the key's permissions rather than inferring them from isolated failures.
Correlation IDs#
Capture a returned request or correlation identifier when the upstream provides one. The existing KlemPay transport also creates a request ID for its own diagnostics and can forward X-Request-ID when configured. A trace identifier helps support locate a request; it is not an authentication credential or an idempotency guarantee.
Record the method, sanitised path, time, HTTP status, application response code, and identifier. Avoid storing raw personal data, full account numbers, request signatures, or secret-bearing URLs.
Diagnose a rejected signature#
- Check that the key and secret belong to the same environment and partner.
- Confirm the clock, millisecond timestamp, uppercase method, and exact versioned path.
- Compare the signed raw body with the actual bytes sent; serialize only once.
- Confirm the key remains active and the endpoint is within its assigned scopes.
- Preserve the backend response message and trace identifier if support is needed.
Do not disable signature verification or relax timestamp validation to make an integration pass. Resolve the mismatch at the request construction boundary.