> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livepeer.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Ingest

> Configuring RTMP ingest for live streams on Livepeer: stream creation, ingest endpoints, transcoding profiles, and webhook events.

export const CenteredContainer = ({children, maxWidth = "800px", padding = "0", preset = "default", width = "", minWidth = "", marginRight = "", marginBottom = "", textAlign = "", style = {}, className = "", ...rest}) => {
  const presets = {
    default: {},
    fitContent: {
      width: "fit-content",
      minWidth: "fit-content"
    },
    readable70: {
      width: "70%",
      minWidth: "fit-content"
    },
    readable80: {
      width: "80%",
      minWidth: "fit-content"
    },
    readable90: {
      width: "90%"
    },
    wide900: {
      maxWidth: "900px"
    }
  };
  const presetStyle = presets[preset] || presets.default;
  return <div className={className} style={{
    maxWidth: presetStyle.maxWidth || maxWidth,
    margin: "0 auto",
    padding: padding,
    ...presetStyle.width ? {
      width: presetStyle.width
    } : {},
    ...presetStyle.minWidth ? {
      minWidth: presetStyle.minWidth
    } : {},
    ...width ? {
      width
    } : {},
    ...minWidth ? {
      minWidth
    } : {},
    ...marginRight ? {
      marginRight
    } : {},
    ...marginBottom ? {
      marginBottom
    } : {},
    ...textAlign ? {
      textAlign
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

export const LinkArrow = ({href, label, description, newline = true, borderColor, className = '', style = {}, ...rest}) => {
  const linkArrowStyle = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: "var(--lp-spacing-1)",
    width: 'fit-content',
    ...borderColor && ({
      borderColor
    })
  };
  return <span className={className} style={style} {...rest}>
      {newline && <br />}
      <span style={linkArrowStyle}>
        <a href={href} target="_blank" rel="noopener noreferrer">
          {label}
        </a>
        <Icon icon="arrow-up-right" size={14} color="var(--lp-color-accent)" />
      </span>
      {description && description}
      {description && <div style={{
    height: "var(--lp-spacing-3)"
  }} />}
    </span>;
};

<CenteredContainer preset="readable90">
  <Tip>Create a stream object to get an RTMP ingest URL and stream key. Push from OBS or FFmpeg to that URL and the network begins transcoding immediately.</Tip>
</CenteredContainer>

***

Livepeer accepts live video via RTMP. A stream object holds the ingest configuration, transcoding profiles, and generates the HLS playback URL. The stream key embedded in the ingest URL authenticates the push; only a client with the correct key can publish to that stream.

<CustomDivider />

## Stream Creation

Create a stream via the SDK:

```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}}
import { Livepeer } from 'livepeer';

const client = new Livepeer({ apiKey: process.env.LIVEPEER_API_KEY });

const { stream } = await client.stream.create({
  name: 'event-stream-2026',
  profiles: [
    { name: '1080p', bitrate: 6000000, fps: 30, width: 1920, height: 1080 },
    { name: '720p',  bitrate: 3000000, fps: 30, width: 1280, height:  720 },
    { name: '360p',  bitrate: 1000000, fps: 30, width:  640, height:  360 },
  ],
  record: false,
});

console.log(stream.rtmpIngestUrl);  // rtmp://rtmp.livepeer.com/live/<streamKey>
console.log(stream.streamKey);      // the key embedded in the ingest URL
console.log(stream.playbackUrl);    // HLS playback, e.g. https://livepeercdn.com/hls/<playbackId>/index.m3u8
```

The same operation in Python:

```python theme={"theme":{"light":"github-light","dark":"dark-plus"}}
from livepeer import Livepeer
from livepeer.models import components

client = Livepeer(api_key=os.environ['LIVEPEER_API_KEY'])

result = client.stream.create(components.NewStreamPayload(
    name='event-stream-2026',
    profiles=[
        components.FfmpegProfile(
            name='720p', bitrate=3000000, fps=30, width=1280, height=720,
        ),
    ],
))
stream = result.stream
```

<CustomDivider />

## Pushing a Stream

Point OBS to the ingest URL and stream key:

* **Server:** `rtmp://rtmp.livepeer.com/live`
* **Stream Key:** value of `stream.streamKey`

With FFmpeg:

```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
ffmpeg -re -i input.mp4 \
  -c:v libx264 -preset veryfast -b:v 3000k \
  -c:a aac -b:a 128k \
  -f flv rtmp://rtmp.livepeer.com/live/<streamKey>
```

The go-livepeer broadcaster Gateway also accepts RTMP on port 1935 when running in broadcaster mode, routing segments directly through the Livepeer Network without a managed API layer.

<CustomDivider />

## Transcoding Profiles

Each profile in the `profiles` array defines one output rendition. The Livepeer Network transcodes the ingest stream into each requested profile in parallel.

Profile fields:

| Field     | Type    | Description                                                                          |
| --------- | ------- | ------------------------------------------------------------------------------------ |
| `name`    | string  | Label for this rendition (e.g. `720p`)                                               |
| `bitrate` | integer | Target video bitrate in bits per second                                              |
| `fps`     | integer | Output frames per second                                                             |
| `width`   | integer | Output width in pixels                                                               |
| `height`  | integer | Output height in pixels                                                              |
| `profile` | string  | H.264 encoder profile: `H264Baseline`, `H264Main`, `H264High`, `H264ConstrainedHigh` |
| `gop`     | string  | GOP length in seconds, or `"intra"` for all-intra                                    |

The HLS manifest at `stream.playbackUrl` lists all renditions. ABR players select the appropriate rendition based on available bandwidth.

<CustomDivider />

## Stream Events

Livepeer fires webhooks on stream lifecycle events. Subscribe to events when creating a stream:

```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}}
const { stream } = await client.stream.create({
  name: 'event-stream',
  profiles: [...],
  playbackPolicy: {
    type: 'webhook',
    webhookId: '<your-webhook-id>',
    webhookContext: { userId: 'user-123' },
  },
});
```

Or create a webhook subscription separately:

```javascript theme={"theme":{"light":"github-light","dark":"dark-plus"}}
const webhook = await client.webhook.create({
  name: 'stream-events',
  url: 'https://your-app.example.com/livepeer-webhook',
  events: ['stream.started', 'stream.idle', 'stream.recording.ready'],
});
```

Key stream events:

| Event                    | Fires when                                    |
| ------------------------ | --------------------------------------------- |
| `stream.started`         | First RTMP frame received; transcoding begins |
| `stream.idle`            | No RTMP data for 60 seconds                   |
| `stream.recording.ready` | Recording asset is ready (if `record: true`)  |

<Note>
  All webhook payloads are signed with an HMAC-SHA256 signature using your webhook secret. Verify the `Livepeer-Signature` header before processing events.
</Note>

<CustomDivider />

## Related Pages

<CardGroup cols={2}>
  <Card title="VOD" icon="film" href="/v2/developers/build/video/vod-and-recording" arrow horizontal>
    Upload and transcode video-on-demand assets via the same SDK.
  </Card>

  <Card title="Video Overview" icon="grid" href="/v2/developers/build/video/overview" arrow horizontal>
    Access paths and workload comparison for Livepeer video.
  </Card>

  <Card title="Transcoding" icon="cpu" href="/v2/developers/build/video/transcoding-direct-quickstart" arrow horizontal>
    Direct transcoding for applications managing their own segments.
  </Card>

  <Card title="Player" icon="play" href="/v2/developers/build/applications/frontend-react-player" arrow horizontal>
    Embed the @livepeer/react Player component to play back HLS streams.
  </Card>
</CardGroup>
