Verify access tokens and handle webhooks in any Python framework.
Python SDK
The ascendkit Python package provides access token verification and webhook signature validation. It works with any Python framework — FastAPI, Flask, Django, Starlette, or plain scripts.
Install
pip install ascendkit
Requires Python 3.10+. Dependencies: httpx, pydantic, pyjwt[crypto].
Access token verification
Verify RS256 access tokens issued by AscendKit using JWKS. Keys are cached in memory for 1 hour.
from ascendkit import AccessTokenVerifier, AuthError
verifier = AccessTokenVerifier()
# Sync
claims = verifier.verify(token)
print(claims["sub"]) # usr_...
print(claims["email"])
# Async
claims = await verifier.verify_async(token)
The constructor reads ASCENDKIT_ENV_KEY from your environment. If the variable is missing, it raises an error at initialization so you catch configuration issues immediately.
Raises AuthError with appropriate status codes:
401— expired or invalid token503— JWKS endpoint unreachable
Framework integration
from fastapi import FastAPI, Depends, Header, HTTPException
from ascendkit import AccessTokenVerifier, AuthError
app = FastAPI()
verifier = AccessTokenVerifier()
async def get_current_user(authorization: str = Header()) -> dict:
token = authorization.removeprefix("Bearer ").strip()
try:
return await verifier.verify_async(token)
except AuthError as e:
raise HTTPException(status_code=e.status_code, detail=str(e))
@app.get("/api/profile")
async def profile(user: dict = Depends(get_current_user)):
return {"email": user["email"], "id": user["sub"]}
from flask import Flask, request
from ascendkit import AccessTokenVerifier
app = Flask(__name__)
verifier = AccessTokenVerifier()
@app.route("/api/profile")
def profile():
token = request.headers.get("Authorization", "").removeprefix("Bearer ").strip()
claims = verifier.verify(token) # sync
return {"email": claims["email"]}
# middleware.py
from ascendkit import AccessTokenVerifier, AuthError
from django.http import JsonResponse
verifier = AccessTokenVerifier()
class AscendKitAuthMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
auth_header = request.META.get("HTTP_AUTHORIZATION", "")
if auth_header.startswith("Bearer "):
token = auth_header.removeprefix("Bearer ").strip()
try:
request.ascendkit_user = verifier.verify(token)
except AuthError as e:
return JsonResponse({"error": str(e)}, status=e.status_code)
else:
request.ascendkit_user = None
return self.get_response(request)
# views.py
from django.http import JsonResponse
def profile(request):
if not request.ascendkit_user:
return JsonResponse({"error": "Not authenticated"}, status=401)
return JsonResponse({"email": request.ascendkit_user["email"]})
Webhook verification
Verify webhook signatures using HMAC-SHA256. Includes timestamp validation (5-minute tolerance) to prevent replay attacks.
from fastapi import Request, Response
from ascendkit import verify_webhook_signature
import os
WEBHOOK_SECRET = os.environ["ASCENDKIT_WEBHOOK_SECRET"]
@app.post("/webhooks/ascendkit")
async def handle_webhook(request: Request) -> Response:
body = await request.body()
signature = request.headers.get("x-ascendkit-signature", "")
if not verify_webhook_signature(
secret=WEBHOOK_SECRET,
signature_header=signature,
payload=body.decode(),
):
return Response(status_code=401, content="Invalid signature")
event = await request.json()
# Handle event by type: user.created, user.approved, etc.
return Response(status_code=200, content="OK")
from flask import request
from ascendkit import verify_webhook_signature
import os
WEBHOOK_SECRET = os.environ["ASCENDKIT_WEBHOOK_SECRET"]
@app.route("/webhooks/ascendkit", methods=["POST"])
def webhook():
if not verify_webhook_signature(
secret=WEBHOOK_SECRET,
signature_header=request.headers.get("x-ascendkit-signature", ""),
payload=request.get_data(as_text=True),
):
return "Invalid signature", 401
event = request.get_json()
return "OK", 200
Server-side analytics
Track events from your backend with trusted identity (secret key auth):
from ascendkit import Analytics
analytics = Analytics()
analytics.track("usr_456", "checkout.completed", {"total": 99.99})
The constructor reads ASCENDKIT_SECRET_KEY from your environment. If missing, it raises an error at initialization.
Events batch in memory and flush every 30 seconds or when the batch fills (10 events). Call analytics.shutdown() for graceful cleanup.