Skip to content

Authentication

One credential, two ways to present it. Both resolve to the same partner and the same scopes, so use whichever suits your stack.

rv_live_9f2a1c7b4e8d0a63_qp3FhK2mN8vX...
│ │ │ │
│ │ │ secret
│ │ key_id, public, safe to log
│ environment: live or test
prefix

Send it as a bearer token:

Terminal window
curl https://api.randaverify.com/v2/me \
-H "Authorization: Bearer rv_live_9f2a1c7b4e8d0a63_qp3F..."

GET /v2/me is the first call worth making. It tells you which organisation the credential belongs to, whether it is live or sandbox, and which scopes it holds.

If your stack expects OAuth, exchange the same key for a short-lived token. client_id is the key_id; client_secret is the secret.

Terminal window
curl -X POST https://api.randaverify.com/oauth/token \
-d "grant_type=client_credentials" \
-d "client_id=9f2a1c7b4e8d0a63" \
-d "client_secret=qp3FhK2mN8vX..."
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 900,
"scope": "verify:nin wallet:read"
}

HTTP Basic works too, if that is what your client sends. Add scope to request a subset of what the key holds, which is how you give one service the least it needs. Asking for a scope the key does not hold is an invalid_scope error rather than a quiet downgrade.

Revoking a key stops its issued tokens on their next request. You do not have to wait for one to expire.

Rotation overlaps. Issuing a replacement puts an expiry on the old key instead of killing it, so you can deploy the new one, confirm it works, and let the old one lapse. The default overlap is 24 hours.

Optional, and recommended if you handle volume. When your key requires signed requests, send three headers:

HeaderValue
X-RV-TimestampUnix seconds. Must be within 5 minutes of ours.
X-RV-NonceUnique per request, 8 to 128 characters.
X-RV-SignatureHex HMAC-SHA256, see below.

The signed string is five lines, newline separated:

POST
/v2/verify/nin
1767225600
5f3c8a1d9e4b2c7f
<sha256 hex of the raw request body>

Signed with the signing secret, which is separate from the key secret and is shown once when the key is issued.

import hashlib, hmac, time, secrets, requests
def signed_post(path, body_bytes, api_key, signing_secret):
timestamp = str(int(time.time()))
nonce = secrets.token_hex(8)
message = "\n".join([
"POST", path, timestamp, nonce,
hashlib.sha256(body_bytes).hexdigest(),
])
signature = hmac.new(signing_secret.encode(), message.encode(),
hashlib.sha256).hexdigest()
return requests.post(
"https://api.randaverify.com" + path, data=body_bytes,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"X-RV-Timestamp": timestamp,
"X-RV-Nonce": nonce,
"X-RV-Signature": signature,
})

Three rules worth knowing. A signature that is present is always verified, whether or not your key requires one, so you can add signing and confirm it works before turning the requirement on. A nonce may not be reused inside the window. And a request whose timestamp is in the future is refused as firmly as a stale one, so check your clock.