How to Test Session Token Expiration for Realtime AI Avatar APIs in CI

CI testing for realtime AI avatar session token expiration: expired-token contract tests, clock mocking, and retry logic.
Introduction
When you wire a realtime AI avatar into CI, session-token expiration is one of the first failure modes that shows up only after the happy path is already working. Locally, a token generated a few minutes ago is usually still valid. In CI, the same flow may run on a slower runner, behind queued jobs, or with flaky network timing, and suddenly your session creation, websocket upgrade, or iframe bootstrap fails with an auth error that is hard to reproduce.
This post shows how to test token expiration deterministically, without waiting for production incidents. By the end, you should be able to write CI checks that verify three things: expired session tokens are rejected, near-expiry tokens behave the way your client expects, and your refresh/retry logic does not accidentally mask real auth problems.
What “session token expiration” actually means in a realtime avatar flow
For realtime avatar APIs, “session token” usually means a short-lived credential used to authorize a session bootstrap or a realtime connection. It is distinct from your long-lived API key. API keys authenticate server-to-server requests; session tokens are often scoped, time-limited, and intended for a single client interaction or embed.
The important part for testing is that expiration is enforced at the moment the server validates the token, not when you created it in test code. In practice, that can happen at a few different boundaries:
creating a session over REST
opening a realtime transport, often over WebRTC or websocket-like signaling
loading a customer-managed iframe and letting it bootstrap its own session
Your CI should cover whichever of those boundaries your app actually uses. If your browser client receives a session token from your backend and immediately hands it to an embedded avatar, then testing only the REST endpoint is not enough. You want the same expired token to fail at the browser-facing edge too.
Design tests around time, not sleeping
The classic mistake is to create a token with a one-minute TTL, call time.sleep(61), and hope CI is stable. It is not. Sleeping makes tests slow, and it still leaves you with race conditions around clock skew, scheduler latency, and network delay.
A better approach is to control time at the edge of your own code and to make the server-side behavior observable. There are three practical strategies:
Inject an explicit expiry when your test setup can create tokens with a very short TTL.
Mock the clock in client code that decides when to refresh or reuse a token.
Assert rejection deterministically by sending a token that is already expired at the moment the server sees it.
The third strategy is the most reliable for CI because it does not depend on waiting. If your test harness can generate a token whose exp or equivalent timestamp is already in the past, you can validate server rejection immediately.
Build a token-expiration test matrix
For a realtime avatar API, I recommend covering these cases:
Valid token: the happy path succeeds.
Just-expired token: the server rejects it with the expected auth error.
Near-expiry token: your client handles it predictably, especially if it retries or refreshes tokens.
Wrong audience or scope: expiration is not the only failure mode, and it is easy to conflate the two.
If your application uses an iframe or browser bootstrap, also test the “expired before page load” case. That catches the real-world bug where a backend-generated token looks fine in the logs but is already stale by the time the user opens the page.
Python test pattern: freeze time and generate an already-expired token
If you use a Python SDK or a small backend helper to mint session credentials, isolate the time logic so you can test it without waiting. The exact fields depend on the API shape in the docs, but the pattern is the same: make token creation accept “now” as a parameter.
The main idea is that you do not need the token issuer itself to know about test clocks. You only need your test helper to create credentials with timestamps that are already invalid. If your production service validates expiry with a small clock-skew window, set the test far enough in the past that the result is unambiguous.
Client-side retry logic: test the boundary, not just the failure
In realtime systems, an expired token is often not just a hard failure. The client may need to fetch a fresh session token, reconnect, and re-establish media. That retry path is where subtle bugs show up: duplicate reconnects, stale UI state, or a token refresh loop that hides a real auth outage.
In CI, treat retry logic as a separate unit of behavior. Don’t only assert that the connection eventually succeeds; assert that it does not attempt to reuse the expired credential after the server has already rejected it.
This kind of test is especially useful if your app talks to a realtime transport. Session establishment is time-sensitive, and a failed connection may leave partially initialized state behind. Your test should verify cleanup as well as retry.
Use contract tests for the server boundary
It helps to separate unit tests from contract tests. Unit tests should cover your client code’s token refresh logic. Contract tests should hit the actual API endpoint that validates the token so you know the platform enforces expiration the way you expect.
A minimal curl-based contract test can be enough to validate the auth boundary in CI:
You would normally replace expired-token with a real token generated by your test setup. The exact session payload and status codes should come from the docs, but the point is to assert that expiration is enforced server-side, not just assumed by your client.
For CI, keep the contract test small and deterministic. It should fail for exactly one reason: the token is invalid because it is expired. Avoid coupling it to voice model behavior, avatar rendering, or any downstream streaming concerns.
How Protoface fits in
Protoface exposes a REST API for creating and managing avatars and realtime sessions, so it is straightforward to test the session boundary directly from CI. If your app uses the Python SDK, you can keep token creation and expiry logic in testable helper functions and then exercise the actual API with a short-lived or already-expired credential. If you are integrating through the LiveKit Agents plugin, the same idea applies one layer higher: verify that an expired session token fails before the avatar is attached to the voice agent.
The useful part here is that you do not need to simulate the whole avatar stack to test expiry. Keep the test focused on auth and session bootstrap. That is where the failure is, and it is much cheaper to diagnose when the test asserts the exact boundary.
Practical CI tips
A few implementation details make these tests much less annoying over time:
Use a dedicated test API key and isolate it from production credentials.
Keep token lifetimes short in test environments so failure modes surface quickly.
Assert on error class or status, not message text unless the message is part of your contract.
Record the server time in logs when a token is rejected; it helps debug clock skew.
Run one contract test per auth boundary instead of duplicating the same check across every avatar flow.
If you have browser-based integration tests, make sure the CI machine clock is sane. Most of the time that just means relying on the runner’s system time, but if you run containerized tests or virtualized clocks, verify they are in UTC and not drifting. Expiration bugs become much harder to reason about when the test environment itself has a skewed clock.
Conclusion
Testing session token expiration is less about waiting for tokens to age out and more about making time a controllable input. Generate already-expired credentials for server-side contract tests, mock the clock in your client logic, and verify that retry paths behave correctly when a realtime session bootstrap is rejected. That gives you fast, deterministic CI coverage for one of the most common failure modes in realtime avatar integrations.
If you are implementing this against Protoface, start with the public docs and the relevant SDK or integration surface for your stack. The goal is simple: catch token-expiration regressions before they ever reach a browser or a voice agent in production.
