Skip to main content

fastapi-webrtc

Typed WebRTC sessions for FastAPI. You declare a session like a route: its factory is an ordinary FastAPI dependency, so authentication, path and query parameters and yield cleanup work as usual. They all run before any peer connection exists. The package exports a contract and generates a typed, framework-free browser client from it.

pip install "fastapi-webrtc[aiortc]"

The core depends on FastAPI, Pydantic and Starlette >=0.41. The Starlette lower bound ensures that an admission dependency's HTTPException returns an HTTP response before accepting the WebSocket. Media engines are adapters; the aiortc extra installs the reference engine. Canon, RPCKit and GStreamer are not dependencies.

Server

from typing import Annotated

from aiortc.contrib.media import MediaRelay
from fastapi import Depends, FastAPI, HTTPException, Query
from pydantic import BaseModel

from fastapi_webrtc import (
    DataChannelSpec,
    Direction,
    IncomingTrack,
    MediaPolicy,
    Peer,
    Session,
    WebRtcRouter,
)
from fastapi_webrtc.engines.aiortc import AiortcEngine


class Prompt(BaseModel):
    text: str


class Feedback(BaseModel):
    text: str
    words: int


# The spec types the channel handles: `peer.channel(MESSAGES)` receives
# `Prompt` and sends `Feedback`.
MESSAGES = DataChannelSpec("messages", receive_model=Prompt, send_model=Feedback)


def lesson_access(lesson_id: str, token: Annotated[str, Query()]) -> str:
    if token != "demo":
        raise HTTPException(status_code=403)  # HTTP 403; no peer is created.
    return lesson_id


rtc = WebRtcRouter(prefix="/rtc", engine=AiortcEngine(), max_sessions=50)


@rtc.session(
    "/lessons/{lesson_id}",
    name="practice",
    media=MediaPolicy(audio=Direction.SEND_RECEIVE),
    data_channels=(MESSAGES,),
)
def practice(lesson: Annotated[str, Depends(lesson_access)]) -> Session:
    relay = MediaRelay()

    async def on_track(peer: Peer, track: IncomingTrack) -> None:
        await peer.send_track(relay.subscribe(track.native))  # Echo the microphone.

    async def on_prompt(peer: Peer, prompt: Prompt) -> None:
        await peer.send_message(
            MESSAGES, Feedback(text=prompt.text, words=len(prompt.text.split()))
        )

    return Session(on_track=on_track, on_message={MESSAGES: on_prompt})


app = FastAPI()
app.include_router(rtc)

A Session is assembled from handlers:

  • on_start(peer): runs once the transport is connected and channels are open.
  • run(peer): the session's main task. When it returns, the session closes normally.
  • on_track(peer, track): called for each incoming track. track.recv() or async for frame in track reads frames.
  • on_message={spec: handler}: validated messages per channel, keyed by its DataChannelSpec or its name. Without a handler, a channel can be consumed with async for message in peer.channel(spec).
  • on_close(peer, code): runs before the engine releases resources.
  • tracks=[...]: outgoing tracks. ice_servers overrides the router's ICE servers.

Peer sends with send_message, send_track and remove_track, which renegotiate. peer.channel(spec) and peer.send_message(spec, message) are typed by the spec's receive_model and send_model; a channel name gives an untyped handle. Reliability options such as ordered are keyword-only.

receive_model=Never or send_model=Never declares a one-way channel. The server drops and reports messages it does not accept, send_message raises WebRtcError(invalid_message), and on_message handlers or async for on a channel without inbound messages are refused. The generated client omits send or subscribe for that direction. Never is not inferred as a type argument; annotate the spec, for example DataChannelSpec[Never, Tick]. It also provides stats(), close(code) and wait_closed(). The session stops tracks it owns when it closes. It never stops tracks passed with owned=False.

Set role=Role.OFFERER when the server should create the offer, for example when the browser only watches.

Browser client

fastapi-webrtc contract --app app:app --output contract.json
fastapi-webrtc generate --contract contract.json --output src/rtc --client-name PracticeRtcClient
import { PracticeRtcClient, isRtcError } from "./rtc/index.js";

const client = new PracticeRtcClient({ baseUrl: "https://api.example.com" });
const microphone = await navigator.mediaDevices.getUserMedia({ audio: true });

const session = await client.practice.connect({
  lessonId: "greetings", // path parameter
  token: "demo", // query parameter
  tracks: microphone, // borrowed: never stopped by the session
  timeoutMs: 10_000,
  onTrack: ({ track }) => {
    audio.srcObject = new MediaStream([track]);
  },
  onMessage: { messages: (feedback) => console.log(feedback.words) },
});

await session.channels.messages.send({ text: "hello" }); // typed as Prompt
await session.close(); // idempotent

timeoutMs limits connection setup, including asynchronous credential providers, and stops applying once the session is ready. Use signal for cancellation of the session's entire lifetime: aborting it also closes an already running audio or video session. An AbortSignal.timeout(...) therefore ends the session when its timer expires, even if connection setup already succeeded.

Later renegotiations, including addTrack() and removeTrack(), use the server's negotiation_timeout_ms and end the session with negotiation_timeout if they cannot complete. Channel sends wait at most sendTimeoutMs (10 seconds by default) for opening or buffer drainage before ending with slow_consumer. Permanent peer-connection failure rejects pending operations with transport_lost.

Failures are RtcErrors with a stable code, such as session_limit, negotiation_timeout, media_rejected or transport_lost. Browsers do not expose the HTTP status of a refused WebSocket handshake. A session denied by a dependency therefore fails with connection_failed. The full list of codes is in the protocol.

Both commands accept --check, which fails on drift without writing. Generated directories contain a manifest. Stale generated files are removed; files you add yourself are kept.

Configure shared credentials with new PracticeRtcClient({ query: async () => ({ token: await refreshToken() }) }). The provider runs for every connection. Query values are merged in this order, with later sources taking precedence: the baseUrl query string, client.config.query, connect({ query: ... }), then named connect parameters such as connect({ lessonId: "greetings", token: "new" }). Use wire aliases as keys inside query maps. An omitted named parameter preserves shared values; null explicitly removes a query value. Required query parameters are checked after merging. Generated query fields are optional so shared credentials can supply them; required path parameters must still be passed.

Security

Session admission is ordinary FastAPI dependency code, so authorization stays in your application. Two WebSocket properties need attention:

  • Origins. CORS does not apply to WebSocket handshakes, and browsers attach cookies to cross-site handshakes. If admission relies on cookies, pass WebRtcRouter(allowed_origins=["https://app.example.com"]). Handshakes from other origins are refused with HTTP 403 before the session factory runs. Requests without an Origin header come from non-browser clients and are not restricted by this check.
  • Query tokens. Browsers cannot set headers on a WebSocket, so tokens travel in the URL. URLs can appear in proxy and server access logs. Prefer short-lived, single-use session tickets issued by an authenticated HTTP endpoint over long-lived access tokens, and supply them through the client's query provider.

Limits and ICE

SessionLimits bounds negotiation time, pending candidates, signaling and message sizes, inbound queues and send buffers. It also sets grace periods for transport and signaling loss. Every limit has a categorized error. TurnRestCredentials issues time-limited TURN REST credentials per user. It can give the server-side engine internal relay URLs.

Documentation

The engine interface abstracts signaling and lifecycle. Native outgoing tracks and received frames remain engine-specific: aiortc uses MediaStreamTrack and PyAV frames. Its statistics return jitter_seconds=None because the public report does not identify the per-stream codec clock rate needed for conversion.

Generated TypeScript models provide compile-time message types; the browser runtime checks formats and sizes but does not enforce every JSON Schema rule. Server-side Pydantic adapters validate incoming and outgoing typed messages.

Metadata

Release files for fastapi-webrtc 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fastapi-webrtc 0.1.0
File Size Uploaded Provenance
fastapi_webrtc-0.1.0.tar.gz 117.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for fastapi-webrtc 0.1.0
File Interpreter ABI Platform
fastapi_webrtc-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 185.7 kB

Release files / fastapi_webrtc-0.1.0.tar.gz

Download URL fastapi_webrtc-0.1.0.tar.gz
Size 117.7 kB
Tags Source
SHA-256 checksum
How to use checksums
fe0042d7aeeacdcec9887b370a9dfd858c94ccaf366f9fc6cbf847642d630aeb
BLAKE2b-256 checksum
How to use checksums
28ede920f12d5665093787d33929feeb45241c9368509991f8a7413b3ce32ad9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / fastapi_webrtc-0.1.0-py3-none-any.whl

Download URL fastapi_webrtc-0.1.0-py3-none-any.whl
Size 67.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1e80755e8ff7a1934152fc600e1809caca74f3aee00f8a2bbe4d1e285a7b4df3
BLAKE2b-256 checksum
How to use checksums
a27adca27096ac6b4f89a5f838ea8a681a382ded9801a55c16306752bdaa17b3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Security for fastapi-webrtc 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for fastapi_webrtc-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page