Authentication
One credential, two ways to present it. Both resolve to the same partner and the same scopes, so use whichever suits your stack.
The key itself
Section titled “The key itself”rv_live_9f2a1c7b4e8d0a63_qp3FhK2mN8vX...│ │ │ ││ │ │ secret│ │ key_id, public, safe to log│ environment: live or testprefixSend it as a bearer token:
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.
OAuth 2.0 client credentials
Section titled “OAuth 2.0 client credentials”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.
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.
Rotating a key
Section titled “Rotating a key”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.
Signing requests
Section titled “Signing requests”Optional, and recommended if you handle volume. When your key requires signed requests, send three headers:
| Header | Value |
|---|---|
X-RV-Timestamp | Unix seconds. Must be within 5 minutes of ours. |
X-RV-Nonce | Unique per request, 8 to 128 characters. |
X-RV-Signature | Hex HMAC-SHA256, see below. |
The signed string is five lines, newline separated:
POST/v2/verify/nin17672256005f3c8a1d9e4b2c7f<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.