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.2.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.2.0
File Size Uploaded Provenance
fastapi_webrtc-0.2.0.tar.gz 131.4 kB With provenance Details

Built distribution (wheel)

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

Total release size: 201.6 kB

Release files / fastapi_webrtc-0.2.0.tar.gz

Download URL fastapi_webrtc-0.2.0.tar.gz
Size 131.4 kB
Tags Source
SHA-256 checksum
How to use checksums
d3db161218835513d26e97b7e8a23ec7d148205903e5d2c4cc96c6eac099a3c3
BLAKE2b-256 checksum
How to use checksums
8c9f2f74364ad69ca74a0270fdfd3573f0e7ad40922ed4c6664ccf9ffe8f3df1
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.2.0-py3-none-any.whl

Download URL fastapi_webrtc-0.2.0-py3-none-any.whl
Size 70.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
54a041db6aaa79123c013a89fc829f9514682834bf82517a7058d629a332eae7
BLAKE2b-256 checksum
How to use checksums
3bce5d7af5d215f899a72f316b4d6ecefb0fc4132dc47571d4d8507916edd3ed
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.2.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.2.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.2.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