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

# Webhooks on system state changes

> Webhooks on system state changes notify apps about live stream, VOD, and restream events without polling.

A webhook is an HTTP POST callback sent to a server-side endpoint when a defined event occurs. The Video Streaming service uses webhooks to notify an app when the state of a live stream, VOD video, or restream changes, eliminating the need to poll the API.

## Webhook types

A single universal endpoint is used for all types of webhooks.

```json theme={null}
{
  "type": "stream|video|video_delete|restream",
  "message": { ... } 
}
```

| Entity | Event | `type` value |
| - | - | - |
| Live stream | State change | `stream` |
| VOD video | State change | `video` |
| VOD video | Deletion | `video_delete` |
| Restream | State change | `restream` |

Each payload contains the main fields needed to identify the entity and process the state change.

## Webhooks for live streams

When the state of a live stream changes, the system triggers a webhook with type `stream`. The payload contains a `message.stream` object. Webhooks fire on key state changes — ingest start, transcoding start, stream stop — and carry enough data to track whether a stream is active, whether playback output is ready, and the stream source (PUSH or PULL).

The state fields change as the stream moves through the following stages:

| Stage | `live` / `backup_live` | transcoding | recording | Description |
| - | - | - | - | - |
| Primary ingest starts | `true` | `false` | `false` | Primary source connects; ingest is active, but playback output is not ready yet. |
| Transcoding starts | `true` | `true` | `false` | Playback output is ready with status 200 OK; use this stage to show or activate a player for users. |
| Recording starts | `true` | `true` | `true` | The live stream is being recorded. |
| Stream stops | `false` | `false` | `false` | Final stage: the stream source disconnects or the broadcast ends, so the stream is not active. |

> [!NOTE]
>
> Use `live: true` (or `backup_live: true`) to track active ingest. Use `transcoding: true` to track when the live stream is ready for playback.
>
> Read more in [stream availability](/streaming/live-streaming/primary-backup#stream-availability).

| Field | Type | Description |
| - | - | - |
| **id** | int | Unique identifier of the stream |
| **live** | bool | `true` while the primary ingest source is connected; `false` after it disconnects |
| **backup\_live** | bool | `true` while the backup ingest source is connected; `false` otherwise |
| **recording** | bool | `true` if the stream is being recorded |
| **transcoding** | bool | `true` if transcoding has started and playback output is ready, `false` by default |
| **client\_user\_id** | string | Optional client-defined user ID, `null` if not provided |
| **client\_entity\_data** | string | Optional client metadata associated with the stream, `null` if not provided |
| **pull** | bool | `true` if the stream is pulled from an external origin, `false` if pushed |
| **uri** | string | The source URI of the stream |
| **stream\_source\_type** | string | Type of the stream source: `rtmp`, `srt`, `webrtc`, `https` |

**Example:**

Stream is ready for playback:

```json theme={null}
{
  "type": "stream",
  "message": {
    "stream": {
      "id": 1001,
      "live": true,
      "backup_live": false,
      "recording": false,
      "client_user_id": 10000001,
      "client_entity_data": "{ 'seq_id': '1234567890', 'name': 'John Doe', 'iat': 1516239022 }",
      "pull": false,
      "uri": "srt://vp-push-ed1-srt.gvideo.co:5001?streamid=12345#aaabbbcccddd",
      "stream_source_type": "srt",
      "transcoding": true
    }
  }
}
```

Stopped stream:

```json theme={null}
{
  "type": "stream",
  "message": {
    "stream": {
      "id": 1002,
      "live": false,
      "backup_live": false,
      "recording": false,
      "client_user_id": 10000001,
      "client_entity_data": "{ 'seq_id': '1234567890', 'name': 'John Doe', 'iat': 1516239022 }",
      "pull": true,
      "uri": "https://.../master.m3u8",
      "stream_source_type": "https",
      "transcoding": false
    }
  }
}
```

## Webhooks for VOD

VOD webhooks use `type: video` for create and status updates, and `type: video_delete` when a video is deleted.

| Webhook type | Status | Description |
| - | - | - |
| `video` | `empty` | Video entity is created, but the source file is still being received. |
| `video` | `pending` | Source file has been received and is being transcoded. |
| `video` | `viewable` | Playback is already available (not all qualities and codecs are ready, so transcoding is still ongoing). |
| `video` | `ready` | All qualities and codecs are processed and available for playback. |
| `video` | `error` | Upload or processing failed; check the `error` field. |
| `video_delete` | Current video status | Video entity was deleted. |

A full description of each status is in [VOD statuses](/streaming/video-hosting/vod-status).

### VOD create and update

The `converted_videos[].status` field tracks each rendition independently of the top-level `status`.

| Field | Type | Description |
| - | - | - |
| **id** | int | Video ID |
| **slug** | string | Public identifier used in playback URLs |
| **name** | string | Video name |
| **duration** | int | Duration of the video in milliseconds |
| **stream\_id** | int \| null | Stream ID if it was recorded from a live stream, otherwise `null` |
| **status** | string | Processing status of the video: `empty`, `pending`, `viewable`, `ready`, `error` |
| **converted\_videos** | array | List of transcoded renditions with details |
| └─ **name** | string | Rendition label (e.g., `vod1080p`) |
| └─ **status** | string | Rendition status |
| └─ **mp4\_url** | string | Direct URL for a rendition in MP4 format |

VOD processing statuses in the API reference:

* Video status — [status](/api-reference/streaming/videos/get-video#response-status)
* Rendition status — [converted\_videos/status](/api-reference/streaming/videos/get-video#response-converted-videos)

**Example:**

Video upload started:

```json theme={null}
{
  "type": "video",
  "message": {
    "video": {
      "id": 2001,
      "slug": "bJkptcIp9a5wrowz",
      "name": "football_videoplayback_720",
      "duration": 43989,
      "stream_id": null,
      "status": "empty",
      "converted_videos": []
    }
  }
}
```

Video becomes viewable while some renditions are still processing:

```json theme={null}
{
  "type": "video",
  "message": {
    "video": {
      "id": 2001,
      "slug": "bJkptcIp9a5wrowz",
      "name": "football_videoplayback_720",
      "duration": 43989,
      "stream_id": null,
      "status": "viewable",
      "converted_videos": [
        {
          "name": "4029_h264_720p",
          "status": "complete",
          "mp4_url": "https://102748.gvideo.io/videos/102748_bJkptcIp9a5wrowz/qid4029v1_h264_2700_720.mp4"
        },
        {
          "name": "4014_hevc_720p",
          "status": "processing",
          "mp4_url": "https://102748.gvideo.io/videos/102748_bJkptcIp9a5wrowz/qid4014v1_hevc_1620_720.mp4"
        },
        {
          "name": "3999_av1_720p",
          "status": "processing",
          "mp4_url": "https://102748.gvideo.io/videos/102748_bJkptcIp9a5wrowz/qid3999v1_av1_1350_720.mp4"
        }
      ]
    }
  }
}
```

### VOD delete

A webhook with type `video_delete` fires when a video is deleted via API or portal. It carries the video ID, slug, name, status at the time of deletion, and deletion timestamp.

| Field | Type | Description |
| - | - | - |
| **id** | int | Video ID |
| **slug** | string | Public identifier used in playback URLs |
| **name** | string | Video name |
| **status** | string | Processing status of the video |
| **deleted\_at** | timestamp | ISO-8601 UTC timestamp of when the deletion was committed in the system |

**Example:**

```json theme={null}
{
  "type": "video_delete",
  "message": {
    "video": {
      "id": 2001,
      "slug": "bJkptcIp9a5wrowz",
      "name": "football_videoplayback_720",
      "status": "ready",
      "deleted_at": "2025-09-16T13:50:42.000Z"
    }
  }
}
```

## Webhooks for restreams

When a restream changes state — starts or stops — the system sends a webhook with type `restream`.
The payload contains a message.restream object.

| Stage | live | Description |
| - | - | - |
| Restream starts | `true` | Restream is active and sends the source live stream to the destination. |
| Restream stops | `false` | Final stage: restream is not active and no longer sends content to the destination. |

| Field | Type | Description |
| - | - | - |
| **id** | int | Unique identifier of the restream. |
| **name** | string | Human-readable name of the restream. |
| **stream\_id** | int | ID of the source live stream that this restream is linked to. |
| **live** | bool | `true` if the restream is currently active, `false` if it stopped. |

**Example:**

Restream starts:

```json theme={null}
{
  "type": "restream",
  "message": {
    "restream": {
      "id": 3001,
      "name": "RTMP from Broadcast#1 to YouTube",
      "stream_id": 1001,
      "live": true
    }
  }
}
```

Restream stops:

```json theme={null}
{
  "type": "restream",
  "message": {
    "restream": {
      "id": 3001,
      "name": "RTMP from Broadcast#1 to YouTube",
      "stream_id": 1001,
      "live": false
    }
  }
}
```

## Webhook delivery

Webhooks are delivered from IP addresses `92.223.112.0/24` and `92.223.123.0/24`. Add these ranges to the server allowlist.

> [!WARNING]
>
> Each event is sent once. If the receiving server is unavailable, the webhook is not retried.

## Enable webhooks

> [!NOTE]
>
> Webhooks require manual setup by Gcore. Contact the support team with the endpoint URL to configure webhooks for the account.

**1. Prepare an HTTP server**

Set up an HTTP server to receive webhooks.

**2. Contact Gcore support**

Contact Gcore support via chat or email [support@gcore.com](mailto:support@gcore.com) and request the webhook integration. Include the following in the request:

* **Account ID** — click the profile icon in the top-right corner of the [Gcore Customer Portal](https://portal.gcore.com/accounts/reports/dashboard). The ID appears next to the account name in the dropdown.

![Gcore Customer Portal profile dropdown showing the account ID](https://mintcdn.com/gcore/YsgyOCumpy-9rxeC/images/docs/streaming/extra-features/get-webhooks-from-the-streaming-platform/webhooks-account-id.png?fit=max\&auto=format\&n=YsgyOCumpy-9rxeC\&q=85\&s=56d91f54e9f9ec25e99a4dc8a42ee775)

* **Webhook endpoint URL** — the server address that will receive the HTTP POST callbacks.

**3. Receive confirmation**

Gcore support will confirm when the webhook integration is configured for the account.

> [!NOTE]
>
> A free tool for inspecting webhook payloads is available at [https://webhook.site](https://webhook.site). It generates a temporary endpoint that captures and displays incoming requests. Send the generated endpoint URL to Gcore support when testing the integration.
