# werift is a programmable WebRTC stack for Node.js in TypeScript

> werift implements WebRTC for Node.js in TypeScript, from a browser-compatible RTCPeerConnection down to ICE, DTLS, SCTP, RTP/RTCP and SRTP, so a server can act as an SFU, a recorder, a gateway or a test peer without wrapping a native browser engine. The npm package requires Node.js 22 or newer, the last push was on 2026-10-20, and version v0.25.1 was published on 2026-10-17.

**shinyoshiaki/werift-webrtc** — WebRTC Implementation for TypeScript (Node.js), includes ICE/DTLS/SCTP/RTP/SRTP/WEBM/MP4

- Repository: https://github.com/shinyoshiaki/werift-webrtc
- Stars: 639 · Forks: 57
- Language: TypeScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/shinyoshiaki-werift-webrtc

## A WebRTC stack that ends at the packet, not at a native binding

werift is a WebRTC implementation for Node.js written in TypeScript, and its pitch is a browser-compatible API sitting on top of protocols you can read. The README describes two ways to use it: use the familiar RTCPeerConnection API when you want to build quickly, then drop down into ICE, DTLS, SCTP, RTP/RTCP, SRTP/SRTCP or individual packets when the server side needs more control than a browser abstraction provides.

The reason to care is stated without hedging. werift does not wrap a native browser engine and does not require a native WebRTC binding, so the whole stack is TypeScript you can instrument, extend and debug. The project lists media servers, gateways, recorders, bots, test peers and custom real-time pipelines as the intended uses.

The feature surface is broad and specific. ICE covers the full protocol plus a local ICE-Lite mode, interoperability with remote ICE-Lite, trickle ICE, ICE restart and ICE-TCP host candidates. TURN is supported as a relay with control transports over UDP, TCP and TLS, including turn: and turns: URL handling. Security is DTLS-SRTP with SRTP and SRTCP and certificate and fingerprint handling. DataChannels are SCTP over DTLS with ordered, unordered and partial reliability options.

The media layer follows RFC 3550 with sender and receiver reports, PLI, Generic NACK, REMB and Transport-Wide CC, plus RTX and RED for resilience, and RTP payload helpers for VP8, VP9, H.264, AV1 and Opus. Simulcast is receive-side, and bandwidth estimation is sender-side and driven by Transport-Wide CC feedback.

The package is MIT licensed and the published npm package declares Node.js 22 or newer.

## One install and one DataChannel example to start from

Installation is a single command:

```bash
npm install werift
```

The quick start in the README is a complete DataChannel exchange between two peer connections in the same process, and it is short enough to read in one go:

```ts
import { RTCPeerConnection } from "werift";

const offerer = new RTCPeerConnection();
const answerer = new RTCPeerConnection();

answerer.ondatachannel = ({ channel }) => {
  channel.onmessage = ({ data }) => {
    console.log("answerer received:", data.toString());
    channel.send("hello from answerer");
  };
};

const channel = offerer.createDataChannel("chat");

channel.onopen = () => {
  channel.send("hello from offerer");
};

channel.onmessage = ({ data }) => {
  console.log("offerer received:", data.toString());
};

await offerer.setLocalDescription(await offerer.createOffer());
await answerer.setRemoteDescription(offerer.localDescription!);

await answerer.setLocalDescription(await answerer.createAnswer());
await offerer.setRemoteDescription(answerer.localDescription!);
```

The negotiation follows the browser shape, with setLocalDescription and setRemoteDescription on both sides. The detail worth knowing is in how candidates are handled. The README explains that setLocalDescription gathers candidates before it resolves, so this compact example can exchange the resulting descriptions without a separate trickle-ICE signalling path. Real applications can instead use onicecandidate and addIceCandidate and move signalling to WebSocket, HTTP, SIP infrastructure, a queue or any other transport.

That sentence explains a design decision. Gathering candidates inside setLocalDescription is unusual next to browser behaviour, and it is what makes a server-to-server exchange possible without running a signalling server, which is exactly what a test peer or an internal media hop needs.

The repository carries runnable examples for this and much more, including datachannel, mediachannel, ice, interop, turn-loopback, save_to_disk and a certificate directory.

## DTLS exports the keys, SRTP carries the media

A detail that confuses newcomers to WebRTC is that media does not travel inside the DTLS connection. The README is explicit: DTLS and SRTP share the ICE transport, but media is not tunnelled through DTLS. DTLS negotiates and exports the keys for SRTP, while the encrypted RTP and RTCP packets flow over the selected ICE path.

The project's own diagram shows the layering. Your application sits on RTCPeerConnection, which handles SDP and negotiation and drives ICE for transport. ICE carries DTLS, and DTLS carries SCTP, which carries the DataChannel. In parallel, ICE carries SRTP, which carries RTP and RTCP onward to a recorder, an SFU, a gateway or a custom media pipeline. The diagram also marks the DTLS-SRTP keying relationship explicitly, since that is the link between the two halves.

The practical consequence is that the media path and the data path are separate, and each can be handled at the layer you need. If you are building a selective forwarding unit, the RTP level is where you make decisions, since the packets are already decryptable there. If you are recording, the same packets are what you write to disk.

This is also why werift describes MediaStreamTrack as able to receive and emit RTP. A track is not a browser concept bound to a media element, it is a source and sink for packets, which is what lets an application connect WebRTC directly to an RTP router, a recorder, a transcoder or a test harness.

## ICE, STUN and TURN are implemented, not borrowed

Server side connectivity is where a WebRTC stack either helps or becomes a wrapper around a C library. werift implements the transport layer in the TypeScript stack, and the README lists ICE-Lite, ICE restart, ICE-TCP and TURN over UDP, TCP and TLS among the modern connectivity features.

ICE-Lite is the mode that matters for a server. A full ICE agent both gathers and responds to connectivity checks, while an ICE-Lite agent only responds, which is what a server behind a public address typically wants. The README lists both a local ICE-Lite mode and interoperability with remote ICE-Lite peers, which suggests the mode is used on both ends in practice.

TURN is the piece that makes a client behind a restrictive network work, and the support here includes the control transports and the URL forms, turn: and turns:, so the same configuration can point at a TURN server over UDP, TCP or TLS.

The modular packaging lets you take a focused package instead of the whole stack, which the README describes as using the full PeerConnection stack or focused ICE, DTLS, SCTP, RTP, and STUN/TURN server packages. For a project that only needs a TURN relay, or only needs to inspect RTP, that is a meaningful difference in dependency surface.

The repository also carries a turn-loopback example, an ice example directory with a restart scenario wired into the example script, and an interop directory, which are the quickest ways to see how the pieces fit together in practice.

## Packet-level control is the reason to choose it over a headless browser

The alternative to a server-side WebRTC stack is a headless browser driven by automation, or a native binding to the browser engine. werift argues against both, and the difference is not just performance.

With a headless browser, the media pipeline is a black box you drive through a debugging protocol, and anything you want to change about congestion control, retransmission or packet handling has to happen outside it. werift's answer is that the core media APIs are packet-oriented, so you can send and receive RTP directly, instrument RTP and RTCP, experiment with congestion logic and inspect transport state without crossing a native boundary.

That is the design that makes the library fit SFUs, recorders, relays, gateways, bots and test peers. A recorder that writes raw RTP, a gateway that rewrites tracks, or a test harness that needs precise packet timing, all need the kind of access a browser API does not offer.

The statistics surface follows the same idea. getStats() returns a browser-compatible report model covering RTP, transport, ICE, codec, certificate, DataChannel and related entries, so existing dashboards and monitoring code can be reused while the underlying implementation stays inspectable.

The repository layout backs this up with dedicated directories for benchmark/, loadtest/, integration/, e2e/ and import-test/, a vitest workspace configuration, and a knip configuration for unused dependency checks, which is the tooling you would expect from a project that treats itself as infrastructure rather than a demo.

## Operating system capture comes from a separate polyfill package

One capability is deliberately not in the core, and it is the one most people expect. werift does not provide operating system camera or microphone capture as part of the core PeerConnection API.

The alternative is documented: applications that need browser-style globals and a register-backed navigator.mediaDevices.getUserMedia() can opt into the werift/polyfill package, whose guide covers installation, cleanup, media registers, constraints and TypeScript entrypoints.

The split is reasonable for a server library. Device capture belongs to the machine, and a server usually has no camera at all, while a test harness or a bot may want the browser-shaped globals to exist. Keeping that out of the core means the core does not depend on platform device APIs.

A related compatibility note sits next to it. The browser-compatible API is not identical to the browser, and the README says a small number of intentional or known edge-case differences remain, either for backward compatibility or because the legacy APIs are unsupported. Those differences are documented separately in the browser API compatibility document, along with the Web Platform Tests strategy used to track exact browser behaviour. That is the document to read before assuming any behaviour match.

For a team, the practical checklist is short. Read the compatibility document for the specific APIs you rely on, and use the polyfill only if your application genuinely needs getUserMedia.

## Interoperability claims come from several directions

A WebRTC implementation that only talks to itself is not much use, and werift argues its interoperability case with several kinds of evidence rather than one.

The README lists Chromium-focused end to end coverage, a Firefox test runner, community-reported Safari interoperability, and independent sipsorcery/webrtc-interop matrices. The last of these is the most telling, because an external project's test matrix measures werift against an implementation that has no interest in werift succeeding.

The repository shows the machinery behind those claims. There is an e2e/ directory with its own install and CI scripts, an examples/e2e directory that installs browsers before running, a website/ directory that builds the documentation and API reference, and a changelog.md. The root package.json drives the whole flow through a ci script that runs type checking, build, test, documentation and the WPT suite together with its coverage variant.

The examples directory is organised the same way, with an examples/package.json and its own lock file, a knip configuration, and a shared util file, and the root scripts include shortcuts such as a datachannel script that runs the offer example with ts-node-dev and a debug script that starts the media channel example with DEBUG=werift* set. That environment variable convention is a small signal of how the code is meant to be debugged.

Maintenance is steady. The repository is not archived, the last push was on 2026-10-20, and the recent releases are v0.25.1 from 2026-10-17, v0.25.0 from 2026-10-07 and v0.24.4 from 2026-08-10. The project is MIT licensed, so the code can be used, modified and redistributed with the notice kept.

## Conclusion

Adopt werift when you need a WebRTC endpoint on a server and want to inspect or change the protocol layers rather than drive a headless browser, especially for recording, routing, gateways and automated test peers. Do not adopt it for browser camera capture out of the box, since the core PeerConnection API does not provide operating system capture and the polyfill is an opt-in package. Verify first by running the datachannel example against a real browser peer and by checking the browser API compatibility document, because the README states that a small number of intentional or edge-case differences from the browser remain.

## FAQ

### What is werift and what does it implement?

werift is a WebRTC implementation for Node.js written in TypeScript, providing a browser-compatible RTCPeerConnection API over its own implementations of ICE, DTLS, SCTP, RTP/RTCP, SRTP/SRTCP and DataChannel. It is MIT licensed and the published npm package declares Node.js 22 or newer.

### Does werift support camera and microphone capture?

Not in the core. The README states that operating system camera and microphone capture is not part of the core PeerConnection API, and points to the werift/polyfill package for applications that need browser-style globals and a register-backed navigator.mediaDevices.getUserMedia().

### How does werift handle ICE candidates without a signalling server?

setLocalDescription gathers candidates before it resolves, so two peers can exchange their descriptions directly without a separate trickle-ICE signalling path. The README notes that real applications can instead use onicecandidate and addIceCandidate and move signalling to WebSocket, HTTP, SIP infrastructure, a queue or another transport.

## Sources

- [Issues](https://github.com/shinyoshiaki/werift-webrtc/issues)
- [License: MIT](https://github.com/shinyoshiaki/werift-webrtc/blob/develop/LICENSE)
- [README](https://github.com/shinyoshiaki/werift-webrtc/blob/develop/README.md)
- [Releases](https://github.com/shinyoshiaki/werift-webrtc/releases)
- [shinyoshiaki/werift-webrtc on GitHub](https://github.com/shinyoshiaki/werift-webrtc)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/shinyoshiaki-werift-webrtc
