Reference

Account tokens

If your signature application serves several customers through one OAuth client, send an account token with each authorization so Cleverbase can attribute the signature to the right customer for accounting. One client, many customers: without the token every signature looks like yours.

You do not need this when your client serves one organization.

How it travels

An account_token parameter on GET /oauth2/authorize, next to the other parameters. It is a JSON Web Token (JWT) you build and sign yourself; Cleverbase verifies it with your client secret.

GET /oauth2/authorize?response_type=code&client_id=<client_id>
    &redirect_uri=<redirect_uri>&scope=service&state=...
    &account_token=<header>.<payload>.<signature>

The token

base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + base64UrlEncode(signature)

Header

FieldRequiredValue
typyesJWT
algyesHS256

Payload

ClaimRequiredMeaning
subyesThe account id, uniquely assigned by your application to that customer.
iatyesIssued at, Unix epoch seconds.
jtiyesA unique identifier for this token.
azpyesAuthorized presenter: your OAuth 2.0 client id.
issnoThe name of your signature application.

Signature

HMAC-SHA256 over base64UrlEncode(header) + "." + base64UrlEncode(payload), with the key being SHA256(client_secret), the raw 32-byte digest of your OAuth client secret, not the secret itself and not its hex or base64 text.

That last detail is where implementations go wrong. In Python:

import base64, hashlib, hmac, json, time, uuid

def b64(raw: bytes) -> str:
    return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()

def account_token(client_id: str, client_secret: str, account_id: str) -> str:
    header = b64(json.dumps({"typ": "JWT", "alg": "HS256"}, separators=(",", ":")).encode())
    payload = b64(json.dumps({
        "sub": account_id,
        "iat": int(time.time()),
        "jti": str(uuid.uuid4()),
        "azp": client_id,
    }, separators=(",", ":")).encode())
    signing_input = f"{header}.{payload}".encode()
    key = hashlib.sha256(client_secret.encode()).digest()   # the digest, not the secret
    sig = hmac.new(key, signing_input, hashlib.sha256).digest()
    return f"{header}.{payload}.{b64(sig)}"

A fresh jti and iat per authorization; reusing a token is not the point of it.

The same construction in the CSC specification is chapter 8.3.1, if you want the source.

Which authorizations carry it

Both legs accept it. Send it on the service leg and on the credential leg of the same flow, with the same sub, so the whole signature is attributed to one customer.

The normative text is CSC API v1, chapter 8.3.1.