Buddy Docs Open Studio

Signed Session Token

No JWT infrastructure? Sign a tiny JSON payload with a shared secret you register in Studio. Buddy verifies the signature in constant time and rejects expired tokens.

Token format

The token is intentionally not a JWT. It is two base64url segments joined by a dot:

token = base64url(JSON payload) + "." + base64url(HMAC_SHA256(payloadB64, secret))

Payload

Include whatever identity you want the agent to know, plus an exp (unix seconds). A userId is required.

{
  "userId": "user_123",
  "email": "ada@example.com",
  "role": "customer",
  "plan": "pro",
  "iat": 1717000000,
  "exp": 1717000300
}

Anything that isn't userId, email, role, exp, iat or nbf becomes a custom attribute. Tokens are capped to a short maximum lifetime by the platform regardless of exp.

Example: mint a token

// Node — no JWT library needed.
import crypto from 'node:crypto';

function signSessionToken(payload, secret) {
  const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
  const sig = crypto.createHmac('sha256', secret).update(body).digest('base64url');
  return body + '.' + sig;
}

const token = signSessionToken(
  { userId: user.id, email: user.email, role: user.role,
    iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 300 },
  process.env.BUDDY_SHARED_SECRET
);
# Python
import base64, hmac, hashlib, json, time

def sign_session_token(payload, secret):
    body = base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b"=")
    sig = hmac.new(secret.encode(), body, hashlib.sha256).digest()
    sig_b64 = base64.urlsafe_b64encode(sig).rstrip(b"=")
    return body.decode() + "." + sig_b64.decode()

Troubleshooting

CodeMeaning & fix
AUTH_TOKEN_MALFORMEDNot two base64url segments split by a dot. Re-check your encoding.
AUTH_TOKEN_INVALIDSignature mismatch. Confirm both sides use the same secret and sign the payload segment bytes.
AUTH_TOKEN_EXPIREDThe exp passed or the token exceeded the platform lifetime ceiling.
AUTH_TOKEN_NO_SUBJECTNo userId in the payload.

Try a token in the live console →