Developers
From API key to first verification in minutes.
One signing scheme for every endpoint, consistent JSON, stable error codes and examples that actually run.
# body.json: {"selfie":{"image":"<base64>"},"reference":{"image":"<base64 KTP>"}}
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH=$(openssl dgst -sha256 -hex body.json | sed 's/^.* //')
SIG=$(printf '%s\n%s\n%s\n%s\n%s' "$TS" "$NONCE" POST /v1/kyc/verify "$BODY_HASH" \
| openssl dgst -sha256 -hmac "$KYC_API_SECRET" -binary \
| openssl base64 -A | tr '+/' '-_' | tr -d '=')
curl -sS "$KYC_BASE_URL/v1/kyc/verify" \
-H "Content-Type: application/json" \
-H "X-Api-Key: $KYC_API_KEY" \
-H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" \
-H "X-Signature: v1=$SIG" \
-H "Idempotency-Key: $(uuidgen)" \
--data-binary @body.jsonconst crypto = require('crypto');
const fs = require('fs');
const { KYC_BASE_URL, KYC_API_KEY, KYC_API_SECRET } = process.env;
async function kycVerify(selfiePath, ktpPath) {
const path = '/v1/kyc/verify';
const body = JSON.stringify({
selfie: { image: fs.readFileSync(selfiePath).toString('base64') },
reference: { image: fs.readFileSync(ktpPath).toString('base64') },
});
const ts = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(16).toString('hex');
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const signature = crypto
.createHmac('sha256', KYC_API_SECRET)
.update([ts, nonce, 'POST', path, bodyHash].join('\n'))
.digest('base64url');
const res = await fetch(KYC_BASE_URL + path, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': KYC_API_KEY,
'X-Timestamp': ts,
'X-Nonce': nonce,
'X-Signature': `v1=${signature}`,
'Idempotency-Key': crypto.randomUUID(),
},
body,
});
return { status: res.status, body: await res.json() };
}<?php
$base = getenv('KYC_BASE_URL');
$key = getenv('KYC_API_KEY');
$secret = getenv('KYC_API_SECRET');
$path = '/v1/kyc/verify';
$body = json_encode([
'selfie' => ['image' => base64_encode(file_get_contents('selfie.jpg'))],
'reference' => ['image' => base64_encode(file_get_contents('ktp.jpg'))],
]);
$ts = (string) time();
$nonce = bin2hex(random_bytes(16));
$canonical = implode("\n", [$ts, $nonce, 'POST', $path, hash('sha256', $body)]);
$sig = rtrim(strtr(base64_encode(hash_hmac('sha256', $canonical, $secret, true)), '+/', '-_'), '=');
$ch = curl_init($base . $path);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"X-Api-Key: $key",
"X-Timestamp: $ts",
"X-Nonce: $nonce",
"X-Signature: v1=$sig",
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$response = json_decode(curl_exec($ch), true);import base64, hashlib, hmac, json, os, secrets, time, uuid
import requests
BASE, KEY, SECRET = os.environ["KYC_BASE_URL"], os.environ["KYC_API_KEY"], os.environ["KYC_API_SECRET"]
def b64file(path):
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode()
path = "/v1/kyc/verify"
body = json.dumps({
"selfie": {"image": b64file("selfie.jpg")},
"reference": {"image": b64file("ktp.jpg")},
}).encode()
ts, nonce = str(int(time.time())), secrets.token_hex(16)
canonical = "\n".join([ts, nonce, "POST", path, hashlib.sha256(body).hexdigest()])
mac = hmac.new(SECRET.encode(), canonical.encode(), hashlib.sha256).digest()
signature = base64.urlsafe_b64encode(mac).decode().rstrip("=")
res = requests.post(BASE + path, data=body, headers={
"Content-Type": "application/json",
"X-Api-Key": KEY,
"X-Timestamp": ts,
"X-Nonce": nonce,
"X-Signature": "v1=" + signature,
"Idempotency-Key": str(uuid.uuid4()),
})
print(res.status_code, res.json()["data"]["decision"])Getting started
Four steps to production
- 01Request access — we activate your dashboard
- 02Create an API key with only the scopes you need
- 03Sign and send your first request
- 04Monitor usage, cost and audit logs
Resources
Everything you need for production
Official SDKs are not available yet — the docs include working examples in four languages.
Start with the docs.
The full reference is public — no sign-in needed.