Documentation
Testing in Postman
The signing scheme has enough moving parts — a timestamp, a body hash, an HMAC signature, a separate bearer token — that hand-building a request to check it works is slow and error-prone. This page sets up one Postman collection-level script that signs every request automatically, so you can confirm the whole flow works in a few minutes before writing any backend code.
1. Create an environment
In Postman, create a new Environment with four variables. Leave access_token blank — it gets filled in automatically once you run the token request in step 4.
base_url—https://api.uverify.uselodger.com/api/v1(use your sandbox client against the same URL — sandbox vs. production is determined by whichclient_keyyou sign with, not a different host).client_key— from your dashboard.api_secret— same place. Mark this variable type assecretin Postman so it doesn't show up in plain text or get synced to a shared workspace.access_token— empty for now.
2. Add the signing script
Open your collection's settings → Scripts tab → Before request (this tab was called Pre-request Script in older Postman versions — same place, new name). This runs before every request in the collection, so you only write it once:
const clientKey = pm.environment.get("client_key");
const apiSecret = pm.environment.get("api_secret");
const timestamp = Math.floor(Date.now() / 1000).toString();
const method = pm.request.method.toUpperCase();
// pm.request.url.getPath() does NOT resolve {{variables}} — if base_url
// itself contains a path (e.g. /api/v1, as in step 1 above), that prefix
// silently disappears and the signed path stops matching what the server
// receives. Resolve the full URL first, then strip the scheme and host.
const resolvedUrl = pm.variables.replaceIn(pm.request.url.toString());
const path = "/" + resolvedUrl
.replace(/^[a-z]+:\/\/[^/]+/i, "")
.split("?")[0]
.replace(/^\/+/, "");
const rawBody = pm.request.body && pm.request.body.mode === "raw" ? pm.request.body.raw : "";
const bodyHash = CryptoJS.SHA256(rawBody).toString(CryptoJS.enc.Hex);
const signingString = [clientKey, timestamp, method, path, bodyHash].join("|");
const signature = CryptoJS.HmacSHA256(signingString, apiSecret).toString(CryptoJS.enc.Hex);
pm.request.headers.upsert({ key: "X-Client-Key", value: clientKey });
pm.request.headers.upsert({ key: "X-Timestamp", value: timestamp });
pm.request.headers.upsert({ key: "X-Signature", value: signature });
// Attach the cached bearer token to every call except the token exchange itself
const token = pm.environment.get("access_token");
if (token && !path.endsWith("/auth/token")) {
pm.request.headers.upsert({ key: "Authorization", value: `Bearer ${token}` });
}CryptoJS is available globally inside Postman's sandbox — no import needed.
Why the script resolves the URL before reading the path
pm.request.url.getPath() on its own only sees the literal part of the request URL — it doesn't resolve {{variables}}. Since base_url already ends in /api/v1, reading the path that way would sign /auth/token while the server checks the signature against /api/v1/auth/token — every request then fails with 401 INVALID_SIGNATURE (never MISSING_HEADERS, since the headers are present and just signed over the wrong string). pm.variables.replaceIn() resolves base_url to its real value first, so the signed path always matches what's actually sent.3. Cache the token automatically
Create a request for POST {{base_url}}/auth/token with an empty body. Open that request's own Scripts tab → After response (called Tests in older Postman versions) and add:
const body = pm.response.json();
if (body.success) {
pm.environment.set("access_token", body.data.access_token);
}Copy this same script onto a POST {{base_url}}/auth/token/refresh request too (also empty body) — running it later refreshes the cached token the same way.
4. Run it
- Run the token request first. Send
POST /auth/token. You should get back{ "success": true, "data": { "access_token": "...", "expires_in": 3600 } }, and youraccess_tokenenvironment variable now has a value — check the eye icon next to the environment selector to confirm. - Then try a protected call. Create
POST {{base_url}}/verifywith JSON body{ "checks": ["nin"], "nin": "12345678901" }(use a real sandbox test ID from your dashboard). Send it — no headers to fill in by hand, the pre-request script already attached all four. - Confirm the signature is actually doing something. Temporarily edit one character of
api_secretin your environment, resend, and confirm you get back401 INVALID_SIGNATURE— then put the correct value back. A quick way to be sure you're not accidentally bypassing the check.
Postman-specific pitfalls
INVALID_SIGNATURE even though all three headers are present
base_url environment variable includes a path segment — it ends in /api/v1, per step 1 — pm.request.url.getPath() alone won't see it, since it only resolves the literal part of the URL, not variables inside it. The script in step 2 already works around this with pm.variables.replaceIn(); if you're running a hand-modified version and hit this, that's the first thing to check. To confirm, add console.log(path) right after the path line and check Postman's Console (bottom-left) — it should print the full path including /api/v1.GET requests must use body mode “none”, not an empty raw body
pm.request.body.mode isn't "raw" — which is what you want for any GET call. If you instead leave the body tab on raw with nothing typed, that still works today, but don't mix the two across requests or a stray space will produce a body hash the server doesn't expect.Folder-level scripts run in addition to the collection one
headers.upsert() is safe to call twice (it replaces, not appends), but keep the signing logic in exactly one place to avoid confusing drift between copies.Don't commit api_secret into a shared collection
api_secret as a secret-type variable so Postman excludes it from exports by default.Once this works
You've now proven the signing scheme end-to-end against a real client. Porting the pre-request script's logic into your own backend language is the entire integration — see the Authentication page for worked examples in cURL, Node.js, PHP, and Python, or Full API reference for every endpoint available once you're authenticated.