Learn how to authenticate your requests using industry-standard JWT with certificate fingerprint Key IDs.
The Xima CCaaS License Provisioning API uses JWT (JSON Web Token) authentication with certificate fingerprint-based Key IDs, following RFC 7515 (JSON Web Signature) and RFC 7517 (JSON Web Key) industry standards. This approach provides strong security for your API requests without requiring traditional mTLS infrastructure.
To authenticate your requests, you'll need to:
kid claimIndustry Standard: This authentication system follows the same JWT kid approach used by major cloud providers like Google Cloud, Microsoft Azure, Auth0, and AWS Cognito for certificate-based authentication.
Important: Never hardcode your private keys in client-side code or share them publicly. This example uses a private key for demonstration purposes only.
Calculate the SHA-1 fingerprint of your client certificate to use as the kid (Key ID) claim.
from cryptography import x509
import hashlib
# Load your client certificate
with open("client-cert.crt", "rb") as f:
cert_data = f.read()
certificate = x509.load_pem_x509_certificate(cert_data)
# Calculate SHA-1 fingerprint of DER encoding
der_bytes = certificate.public_bytes(encoding=x509.Encoding.DER)
kid = hashlib.sha1(der_bytes).hexdigest()
# Optional: Format for display (with colons)
formatted_fingerprint = ':'.join(kid[i:i+2] for i in range(0, len(kid), 2)).upper()
print(f"Certificate fingerprint: {formatted_fingerprint}")
print(f"Kid for JWT: {kid}")
Note: The kid must be the SHA-1 hash of your certificate's DER encoding (without colons). This fingerprint must match a certificate registered with Xima.
Create a JSON object containing the license parameters you want to update, including optional AI messaging, AI audio services, EHR/EMR integration seats, and SIP credentials.
# Create the license payload with ccId, aiMessaging, aiAudioServices, ehrEmrIntegrationSeats, and sipCredentials objects
license_payload = {
"ccId": {
"resellerId": "RESELLER001",
"resellerName": "Acme Distribution",
"tenantId": "TENANT456",
"tenantName": "West Coast Sales"
},
"aiMessaging": {
"standardBundles": 5,
"advancedBundles": 0
},
"aiAudioServices": {
"additionalHours": 25
},
"ehrEmrIntegrationSeats": 1,
"digitalOnlySeats": 15,
"essentialSeats": 0,
"professionalSeats": 25,
"eliteSeats": 10,
"workforceOptimizationSeats": 10,
"speechAnalyticsSeats": 5,
"dialerSeats": 0,
"sipCredentials": {
"username": "user123",
"password": "secret",
"sipPort": 5061,
"sipServer": "sip_server.example.com",
"transport": "udp",
"outboundProxy": "outbound.example.com"
}
}
Serialize the JSON with specific parameters and calculate its SHA-256 hash.
import json
import hashlib
# Serialize JSON with specific parameters
payload_string = json.dumps(license_payload, sort_keys=False, separators=(',', ':')).encode('utf-8')
# Calculate SHA-256 hash
payload_hash = hashlib.sha256(payload_string).hexdigest()
Note: The serialization parameters are critical. You must use sort_keys=False and separators=(',', ':') for the server to calculate the same hash.
Build the JWT header and payload with standard RFC claims and certificate fingerprint.
import base64
import uuid
import time
# Generate a unique JTI
jti = str(uuid.uuid4())
# Set timestamps
current_time = int(time.time())
expiration_time = current_time + (30 * 60) # 30 minutes in the future
# JWT Header with certificate fingerprint
jwt_header = {
"alg": "RS256",
"typ": "JWT",
"kid": kid # Certificate fingerprint from Step 1
}
# JWT Payload with RFC 7519 standard claims
jwt_payload = {
"iss": "xima-ccaas", # Issuer (required)
"sub": kid, # Subject - certificate fingerprint
"aud": "xima-ccaas", # Audience (required)
"payload_hash": payload_hash, # Request body hash
"jti": jti, # JWT ID - unique identifier
"exp": expiration_time, # Expiration time
"iat": current_time # Issued at time
}
# Base64URL encoding function
def base64url_encode(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b'=').decode('utf-8')
# Encode header and payload
header_encoded = base64url_encode(json.dumps(jwt_header).encode('utf-8'))
payload_encoded = base64url_encode(json.dumps(jwt_payload).encode('utf-8'))
RFC 7519 Claims: The issuer (iss) and audience (aud) must be "xima-ccaas". The subject (sub) should be your certificate fingerprint. Each JTI must be unique to prevent replay attacks.
Sign the encoded header and payload with your private key using RS256 algorithm.
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
# Load your private key (matching the certificate)
from cryptography.hazmat.primitives.serialization import load_pem_private_key
with open("client-key.pem", "rb") as f:
private_key = load_pem_private_key(f.read(), password=None)
# Create message to sign
message = f"{header_encoded}.{payload_encoded}".encode('utf-8')
# Sign with RS256 (PKCS#1 v1.5 padding + SHA-256)
signature = private_key.sign(
message,
padding.PKCS1v15(),
hashes.SHA256()
)
# Encode the signature
signature_encoded = base64url_encode(signature)
# Combine to form complete JWT
jwt = f"{header_encoded}.{payload_encoded}.{signature_encoded}"
RS256 Algorithm: Uses PKCS#1 v1.5 padding with SHA-256 hashing, following RFC 7518 standards for JWT signatures.
Make the API request with the JWT in the Authorization header following OAuth 2.0 Bearer Token Usage (RFC 6750).
import requests
# API endpoint
api_url = "https://api.ximadev.cloud/v1/licensing/update"
# Headers with JWT Bearer token (RFC 6750)
headers = {
"Authorization": f"Bearer {jwt}",
"Content-Type": "application/json"
}
# Send request with serialized payload
response = requests.post(
api_url,
headers=headers,
data=payload_string # This is the serialized license payload
)
# Process response
if response.status_code == 200:
print("Success!")
print(response.json())
else:
print(f"Error: {response.status_code}")
print(response.text)
OAuth 2.0 Bearer: The Authorization header follows RFC 6750 Bearer Token Usage standard for transmitting JWTs in HTTP requests.
AI Messaging: Configure billable AI messaging by specifying the number of additionalMessageBundles (each bundle = 1000 messages).
AI Audio Services: Configure by specifying additionalHours (number of additional AI transcription hours).
EHR/EMR Integration: Use ehrEmrIntegrationSeats to specify the number of agent users with EHR/EMR integration.
Professional and Elite seats include a base number of AI messages and AI audio hours per agent. The aiMessaging.additionalMessageBundles and aiAudioServices.additionalHours fields are for purchasing additional capacity beyond the included base. Each message bundle = 1000 messages.
If SIP registration is required, include the optional sipCredentials object with the following properties: username, password, sipServer, outboundProxy (all required). sipPort and transport are optional but must be provided together - when omitted, SRV lookup is used.
{
"ccId": {
"resellerId": "RESELLER001",
"resellerName": "Acme Distribution",
"divisionId": "TENANT456",
"divisionName": "West Coast Sales"
},
"aiMessaging": {
"standardBundles": 5,
"advancedBundles": 0
},
"aiAudioServices": {
"additionalHours": 20
},
"sipCredentials": {
"username": "user123",
"password": "secret",
"sipServer": "sip_server.example.com",
"sipPort": 5061,
"transport": "udp",
"outboundProxy": "outbound.example.com"
}
}
Here's a complete Python example that puts everything together into a reusable function.
import json
import hashlib
import uuid
import time
import base64
import requests
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import load_pem_private_key
def update_license(certificate_path, private_key_pem, license_data, api_url, expiration_minutes=30):
"""
Update license using the Xima CCaaS API with JWT kid authentication (RFC 7515/7517)
Args:
certificate_path (str): Path to client certificate file (.crt)
private_key_pem (str): PEM-encoded private key
license_data (dict): License payload
api_url (str): API endpoint URL
expiration_minutes (int): Minutes until expiration (default: 30, max: 30)
Returns:
dict: API response data
"""
from cryptography import x509
# Step 1: Calculate certificate fingerprint for kid
with open(certificate_path, "rb") as f:
cert_data = f.read()
certificate = x509.load_pem_x509_certificate(cert_data)
der_bytes = certificate.public_bytes(encoding=x509.Encoding.DER)
kid = hashlib.sha1(der_bytes).hexdigest()
# Step 2: Load private key
private_key = load_pem_private_key(
private_key_pem.encode('utf-8'),
password=None
)
# Ensure expiration is within limits
expiration_minutes = min(expiration_minutes, 30)
# Step 3: Calculate timestamps
current_time = int(time.time())
expiration_time = current_time + (expiration_minutes * 60)
# Generate unique JTI
jti = str(uuid.uuid4())
# Step 4: Serialize payload with specific parameters
payload_string = json.dumps(license_data, sort_keys=False, separators=(',', ':')).encode('utf-8')
payload_hash = hashlib.sha256(payload_string).hexdigest()
# Step 5: Base64URL encode function
def base64url_encode(data):
return base64.urlsafe_b64encode(data).rstrip(b'=').decode('utf-8')
# Step 6: Create JWT header and payload with RFC 7519 claims
jwt_header = {
"alg": "RS256",
"typ": "JWT",
"kid": kid # Certificate fingerprint
}
# JWT payload with standard RFC claims
jwt_payload = {
"iss": "xima-ccaas", # Issuer (required)
"sub": kid, # Subject - certificate fingerprint
"aud": "xima-ccaas", # Audience (required)
"payload_hash": payload_hash,
"jti": jti, # JWT ID - unique identifier
"exp": expiration_time, # Expiration time
"iat": current_time # Issued at time
}
# Step 7: Encode header and payload
header_encoded = base64url_encode(json.dumps(jwt_header).encode('utf-8'))
payload_encoded = base64url_encode(json.dumps(jwt_payload).encode('utf-8'))
# Step 8: Sign the JWT with RS256
message = f"{header_encoded}.{payload_encoded}".encode('utf-8')
signature = private_key.sign(
message,
padding.PKCS1v15(),
hashes.SHA256()
)
signature_encoded = base64url_encode(signature)
# Step 9: Build complete JWT
jwt = f"{header_encoded}.{payload_encoded}.{signature_encoded}"
# Step 10: Send authenticated request (RFC 6750 Bearer Token)
headers = {
"Authorization": f"Bearer {jwt}",
"Content-Type": "application/json"
}
response = requests.post(
api_url,
headers=headers,
data=payload_string
)
# Step 11: Handle response
if response.status_code == 200:
return response.json()
else:
raise Exception(f"API Error ({response.status_code}): {response.text}")
If you receive an INVALID_SIGNATURE error, check the following:
sort_keys=False, separators=(',', ':'))kid in your JWT header matches the one provided by XimaJWT tokens have time-based validation. If you receive an INVALID_TOKEN error, check:
The API enforces certain business rules for license configurations:
workforceOptimizationSeats and workforceManagementSeats require at least 10 seatsspeechAnalyticsSeats and dialerSeats require at least 5 seatsadditionalAiMessages requires at least one eliteSeatsNow that you understand how to authenticate your API requests, you can:
Explore the full API reference documentation to understand all available endpoints and parameters.
Try the interactive examples in Google Colab to see the full authentication flow, including CSR generation and certificate handling.