Restream sends your live stream to other platforms, like YouTube, Twitch, Facebook or LinkedIn, at the same time as your own app. Your viewers keep watching in your app over RTC (or HLS for broadcast-mode streams), and each platform gets its own copy over RTMP or RTMPS.
Where this fits: you already create streams and add hosts from your backend (Streams & lifecycle). A restream destination is one more thing your backend attaches to a stream. Your app and your hosts don't change at all.
How it works
- Livqeno joins the stream's room as a hidden viewer and mixes every host's audio. With a single H.264 host (OBS ingest, or a browser host, which publishes H.264 for live streams), that host's own video is copied, not re-encoded. With several hosts, or a screen share, they're composited onto one 1280×720 frame and encoded once (H.264 with 2-second keyframes, plus AAC audio).
- That one encode is copied to every destination. Adding or removing a destination never restarts the encoder, so HLS viewers see no glitch.
- Each destination is isolated. If YouTube rejects your key, Twitch and HLS keep going.
- It works for both delivery modes. An
RTC_ONLYstream starts the encoder when its first destination is enabled and stops it when the last one is removed.
The layout is a fixed grid: one host fills the frame, two sit side by side, three or four share a 2×2 grid, and a screen share takes the main area with cameras in a strip. Custom layouts aren't available.
Add a destination
You need two things from the platform: the server URL and the stream key. Pass them separately. Don't paste the key onto the end of the URL.
const destination = await livqeno.liveStreams.restreams.create(stream.id, {
url: 'rtmps://a.rtmp.youtube.com/live2',
streamKey: process.env.YOUTUBE_STREAM_KEY!,
label: 'YouTube',
});from livqeno import CreateRestreamParams
destination = livqeno.live_streams.restreams.create(
stream["id"],
CreateRestreamParams(
url="rtmps://a.rtmp.youtube.com/live2",
stream_key=os.environ["YOUTUBE_STREAM_KEY"],
label="YouTube",
),
)curl -X POST "$LIVQENO_API_URL/v1/live-streams/$STREAM_ID/restreams" \
-H "Authorization: Bearer $LIVQENO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "rtmps://a.rtmp.youtube.com/live2", "streamKey": "xxxx-xxxx-xxxx-xxxx", "label": "YouTube"}'If the stream is already LIVE, Livqeno starts pushing right away.
Otherwise the destination waits in IDLE and starts when the stream does.
The response is the destination, without its key:
{
"id": "rst_8sW1qL0pZ3mXc2",
"label": "YouTube",
"url": "rtmps://a.rtmp.youtube.com/live2",
"keyLast4": "xxxx",
"enabled": true,
"status": "IDLE",
"lastError": null,
"attempts": 0,
"connectedAt": null,
"createdAt": "2026-10-02T12:00:00.000Z",
"updatedAt": "2026-10-02T12:00:00.000Z"
}Server URLs for common platforms
| Platform | Server URL | Where the key is |
|---|---|---|
| YouTube | rtmps://a.rtmp.youtube.com/live2 | YouTube Studio → Go live → Stream → Stream key |
| Twitch | rtmp://live.twitch.tv/app (Twitch also lists RTMPS and regional ingest servers in its help center) | Creator Dashboard → Settings → Stream → Primary stream key |
rtmps://live-api-s.facebook.com:443/rtmp | Live Producer → Streaming software → Stream key |
Plain rtmp:// works too, but use rtmps:// wherever the platform offers
it, so the key isn't sent in clear text.
List, pause and remove
const destinations = await livqeno.liveStreams.restreams.list(stream.id);
// Pause one destination mid-stream, then resume it.
await livqeno.liveStreams.restreams.update(stream.id, destination.id, { enabled: false });
await livqeno.liveStreams.restreams.update(stream.id, destination.id, { enabled: true });
// Stop pushing and remove it.
await livqeno.liveStreams.restreams.delete(stream.id, destination.id);from livqeno import UpdateRestreamParams
destinations = livqeno.live_streams.restreams.list(stream["id"])
livqeno.live_streams.restreams.update(stream["id"], destination["id"], UpdateRestreamParams(enabled=False))
livqeno.live_streams.restreams.update(stream["id"], destination["id"], UpdateRestreamParams(enabled=True))
livqeno.live_streams.restreams.delete(stream["id"], destination["id"])curl "$LIVQENO_API_URL/v1/live-streams/$STREAM_ID/restreams" \
-H "Authorization: Bearer $LIVQENO_API_KEY"
curl -X PATCH "$LIVQENO_API_URL/v1/live-streams/$STREAM_ID/restreams/$RESTREAM_ID" \
-H "Authorization: Bearer $LIVQENO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
curl -X DELETE "$LIVQENO_API_URL/v1/live-streams/$STREAM_ID/restreams/$RESTREAM_ID" \
-H "Authorization: Bearer $LIVQENO_API_KEY"PATCH changes only enabled and label. To change the URL or the key,
delete the destination and add it again.
The four routes are:
| Route | Does |
|---|---|
POST /v1/live-streams/{streamId}/restreams | Add a destination (url, streamKey, optional label) |
GET /v1/live-streams/{streamId}/restreams | List destinations with their status |
PATCH /v1/live-streams/{streamId}/restreams/{restreamId} | Pause or resume (enabled), or rename (label) |
DELETE /v1/live-streams/{streamId}/restreams/{restreamId} | Stop and remove |
Statuses
status | Means |
|---|---|
IDLE | Configured, but the stream isn't live yet |
CONNECTING | Livqeno is connecting to the platform |
ACTIVE | The platform is receiving video |
RETRYING | The connection dropped or was refused. Livqeno retries after 1, 2, 4 … seconds, up to 30 seconds apart |
FAILED | 10 attempts in a row failed. Livqeno stops trying. lastError says why |
STOPPED | Paused with enabled: false, or the stream ended |
attempts counts consecutive failures, and goes back to 0 once a connection
has stayed up for 30 seconds. The most common cause of FAILED is a wrong or
expired stream key. Fix it on the platform, then delete the destination and
add it again with the new key.
Webhooks
| Event | Sent when | data |
|---|---|---|
live_stream.restream_active | A destination started receiving video | streamId, restreamId, label, at |
live_stream.restream_failed | A destination reached FAILED | streamId, restreamId, label, reason, at |
A failed destination never affects the stream itself, HLS, or the other
destinations, so restream_failed is something to show your host ("YouTube
disconnected: check your stream key"), not a reason to end the stream. See
Stream events for how to subscribe.
Limits
- Up to 5 destinations per stream.
rtmp://andrtmps://URLs only. URLs that point at private, loopback or link-local addresses are rejected.- Output is a fixed-grid composite at 1280×720, 30 fps.
- Each platform applies its own rules, for example YouTube's warning when the keyframe interval is above 4 seconds. Livqeno always sends 2-second keyframes, so that one never fires.
Next steps
- OBS & encoder ingest: broadcast into a stream from OBS, then restream it out.
- Stream events: every live-stream webhook.
- Known limitations: what restream and ingest don't do yet.