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-SignatureHMAC_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.