Authentication
Bearer secret keys
Authenticate every request with your secret key:
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxx
Keys are shown once at creation and stored hashed — keep them server-side and
never embed them in client apps. sk_test_ keys work only on the sandbox;
sk_live_ keys only in production. Keys are scoped (collections:write,
transfers:write, read) and rotatable.
HMAC request signing
High-risk endpoints (/transfers, VAS purchases) additionally require a
signature. Send two headers:
X-Starpay-Timestamp— current unix time (seconds)X-Starpay-Signature—HMAC_SHA256(signing_secret, "{timestamp}.{method}.{path}.{idempotency_key}.{sha256(body)}")
The Idempotency-Key you send on the request is part of the signed string, so a
captured signature can't be replayed under a different key. Sign with the same
Idempotency-Key you put in the header. Requests are rejected if the timestamp is
more than 300 seconds old.
cURL
TS=$(date +%s)
IDEM=$(uuidgen)
BODY='{"amount":250000,"currency":"GHS","msisdn":"0240000000"}'
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 | awk '{print $2}')
SIG=$(printf '%s' "$TS.POST./api/v1/transfers.$IDEM.$BODY_HASH" \
| openssl dgst -sha256 -hmac "$STARPAY_SIGNING_SECRET" | awk '{print $2}')
curl -X POST https://api.starpay.example/api/v1/transfers \
-H "Authorization: Bearer $STARPAY_SECRET_KEY" \
-H "Idempotency-Key: $IDEM" \
-H "X-Starpay-Timestamp: $TS" \
-H "X-Starpay-Signature: $SIG" \
-H "Content-Type: application/json" \
-d "$BODY"
Python
import hashlib, hmac, time
def sign(secret: str, method: str, path: str, body: bytes, idempotency_key: str) -> tuple[str, str]:
ts = str(int(time.time()))
body_hash = hashlib.sha256(body).hexdigest()
message = f"{ts}.{method}.{path}.{idempotency_key}.{body_hash}"
sig = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()
return ts, sig
Node.js
const crypto = require("crypto");
function sign(secret, method, path, body, idempotencyKey) {
const ts = Math.floor(Date.now() / 1000).toString();
const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
const message = `${ts}.${method}.${path}.${idempotencyKey}.${bodyHash}`;
const sig = crypto.createHmac("sha256", secret).update(message).digest("hex");
return { ts, sig };
}
PHP
function starpay_sign(string $secret, string $method, string $path, string $body, string $idempotencyKey): array {
$ts = (string) time();
$bodyHash = hash('sha256', $body);
$message = "{$ts}.{$method}.{$path}.{$idempotencyKey}.{$bodyHash}";
$sig = hash_hmac('sha256', $message, $secret);
return [$ts, $sig];
}
Optionally restrict keys to an IP allowlist, enforced at the auth layer.