English
English
Appearance
English
English
Appearance
This guide is written for beginner developers so you can send valid HMAC requests quickly.
CredentialType=service, CredentialId=<service-id>x-date;x-request-id;x-client-typeCredentialType=user, CredentialId=<user-email>X-Tenant-Id.x-date;x-tenant-id;x-request-id;x-client-typeEvery request must include:
AuthorizationX-Date (ISO-8601 UTC, example: 2026-03-16T08:30:12.000Z)X-Request-Id (UUID, always new per request)X-Client-TypeFor user auth, also include:
X-Tenant-IdThe flow is always the same:
HMAC-SHA256
<X-Date>
<hash-canonical-request>import { createHash, createHmac, randomUUID } from "node:crypto";
type CredentialType = "service" | "user";
function signedHeaders(type: CredentialType): string[] {
return type === "user"
? ["x-date", "x-tenant-id", "x-request-id", "x-client-type"]
: ["x-date", "x-request-id", "x-client-type"];
}
function sign(params: {
method: string;
url: string;
body: string;
secretKey: string;
date: string;
requestId: string;
clientType: string;
tenantId?: string;
credentialType: CredentialType;
}) {
const u = new URL(params.url);
const headers = signedHeaders(params.credentialType);
const headerValue = (h: string) => {
switch (h) {
case "x-date":
return params.date;
case "x-tenant-id":
return params.tenantId ?? "";
case "x-request-id":
return params.requestId;
case "x-client-type":
return params.clientType;
default:
return "";
}
};
const canonicalHeaders =
headers.map((h) => `${h}:${headerValue(h)}`).join("\n") + "\n";
const payloadHash = createHash("sha256").update(params.body).digest("hex");
const canonicalRequest = [
params.method.toUpperCase(),
u.pathname,
u.search.slice(1),
canonicalHeaders,
headers.join(";"),
payloadHash,
].join("\n");
const stringToSign = [
"HMAC-SHA256",
params.date,
createHash("sha256").update(canonicalRequest).digest("hex"),
].join("\n");
return createHmac("sha256", params.secretKey)
.update(stringToSign)
.digest("hex");
}
const date = new Date().toISOString();
const requestId = randomUUID();Authorization: HMAC-SHA256 CredentialType=service, CredentialId=external-system-client, SignedHeaders=x-date;x-request-id;x-client-type, Signature=<hex-signature>
X-Date: 2026-03-16T08:30:12.000Z
X-Request-Id: 231b0de6-5d4b-4ad9-a7c5-03c2f0cba80f
X-Client-Type: serviceAuthorization: HMAC-SHA256 CredentialType=user, CredentialId=admin@example.com, SignedHeaders=x-date;x-tenant-id;x-request-id;x-client-type, Signature=<hex-signature>
X-Date: 2026-03-16T08:30:12.000Z
X-Tenant-Id: tenant-123
X-Request-Id: 0126f7aa-31bc-4c84-91ef-7b7c51f594f8
X-Client-Type: webX-Date is valid UTC ISO-8601.X-Request-Id is new for every request, including retries.SignedHeaders order matches server rules exactly.\n)."" as the body.CredentialType + CredentialId format.401 Invalid Authorization format -> wrong Authorization structure.401 Missing required headers -> one or more required headers are missing.401 Signature expired -> clock drift (selisih waktu) is too large.401 Invalid signature -> client canonical request differs from server.401 Replay request detected -> same request ID was reused.