Partner API base path is /api/v1 on https://api.dcast.pro. This site documents the live surface; use the Quickstart QA curls to validate keys and routes.

Know when a video is ready or a stream goes live (webhooks or polling)

Processing a video takes time, and a stream goes live when the encoder does. Your program should not guess: either DCAST calls a web address of yours when it happens (a webhook), or your program asks for the state on a timer (polling). This page shows both, with the calls in order.

The short version: register an HTTPS address with POST /me/webhooks and the events you want, store the signingSecret from the answer (it is shown once), check the Dcast-Signature header of every delivery against the raw body, and answer 2xx within 10 seconds. No public address? Read GET /videos/:id/status or GET /streams/:id/status instead.

Steps in order

  1. See the events you can subscribe to: GET /me/webhooks/events. A wildcard such as video.* or stream.* subscribes to a whole family, and an empty list subscribes to everything.
  2. Register your address: POST /me/webhooks with url (HTTPS, a public name that resolves), events and a description. Store signingSecret on your server at once: the API returns it only in this answer.
  3. Test it: POST /me/webhooks/:id/test sends a webhook.test event to that address only, and POST /me/webhooks/:id/simulate renders any event with its signature without sending anything.
  4. Read what happened: GET /me/webhooks/:id/deliveries lists every attempt with the answer of your server (responseStatus, errorMessage, durationMs, attemptNumber).
  5. Receive the events: check the signature of each delivery (below), then act on type and data. video.processing.completed carries data.videoId, and video.processing.failed carries data.error.
  6. With no public address, poll instead: GET /videos/:id/status until status is READY or FAILED, and GET /streams/:id/status for isLive and hasSignal.

The calls

CallAuthWhat you get
GET /me/webhooks/eventskeydata.events, every event type you can subscribe to.
POST /me/webhookskeyThe webhook: id, url, events, isActive, and signingSecret once.
POST /me/webhooks/:id/testkey202 with an eventId; the delivery arrives a moment later.
POST /me/webhooks/:id/simulatekeyrenderedRequest: the headers (with Dcast-Signature) and the bodyString a delivery would carry. Nothing is sent.
GET /me/webhooks/:id/deliverieskeydata, newest first, and pagination.nextCursor for the next page.
GET /webhooks/ipspublicdata.ips, the address deliveries leave from, and a note on how far to rely on it for a firewall.
GET /videos/:id/statuskeystatus, progress and stages.
GET /streams/:id/statuskeyisLive, hasSignal and viewersCount.

Example with curl

# 1. The events you can subscribe to  → data.events
curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" "https://api.dcast.pro/api/v1/me/webhooks/events"

# 2. Register your address  → data.id, data.signingSecret (shown once)
curl -s -X POST "https://api.dcast.pro/api/v1/me/webhooks" -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/dcast-webhook","events":["video.processing.completed","video.processing.failed"],"description":"WEBHOOK_DESCRIPTION"}'

# 3. Send a test event to it  → 202, data.eventId
curl -s -X POST -H "Authorization: Bearer pk_YOUR_KEY_HERE" "https://api.dcast.pro/api/v1/me/webhooks/WEBHOOK_ID/test"

# 4. What your server answered  → data[].responseStatus, data[].errorMessage
curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" "https://api.dcast.pro/api/v1/me/webhooks/WEBHOOK_ID/deliveries"

# Without a public address: poll the state instead  → data.status (READY, FAILED, ...)
curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" "https://api.dcast.pro/api/v1/videos/VIDEO_ID/status"

The events of a video and a stream

video.upload.completed means the bytes arrived (data.videoId, fileSize, uploadedAt). video.processing.completed carries data.videoId, title and completedAt, and video.processing.failed carries data.videoId, title, error and failedAt. For a stream, stream.broadcast_started and stream.broadcast_stopped say that viewers can or can no longer watch, while stream.live and stream.ended say that the encoder signal arrived or was lost. The payloads of the stream events are in Webhooks.

What a delivery looks like

Every delivery is an HTTPS POST with a JSON body (id, type, created, livemode, data) and the headers Dcast-Signature (t=...,v1=...), Dcast-Event-Id and Dcast-Event-Type. v1 is the hex HMAC-SHA256 of t, a dot and the raw body, keyed with your signingSecret. Reject a delivery whose t is more than 300 seconds from your clock. Read the raw body before you parse the JSON, because the signature covers the exact bytes.

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const handled = new Set(); // keep these in a database in production

app.post('/dcast-webhook', express.raw({ type: '*/*' }), (req, res) => {
  const fields = Object.fromEntries(
    String(req.get('Dcast-Signature') || '').split(',').map((part) => part.trim().split('=')),
  );
  const raw = req.body.toString('utf8');
  const expected = crypto
    .createHmac('sha256', process.env.DCAST_WEBHOOK_SECRET)
    .update(fields.t + '.' + raw)
    .digest('hex');
  const given = Buffer.from(String(fields.v1 || ''), 'hex');
  const wanted = Buffer.from(expected, 'hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(fields.t)) <= 300;
  if (!fresh || given.length !== wanted.length || !crypto.timingSafeEqual(given, wanted)) {
    return res.status(400).end();
  }
  const event = JSON.parse(raw);
  if (handled.has(event.id)) return res.status(200).end();
  handled.add(event.id);
  if (event.type === 'video.processing.completed') {
    // event.data.videoId: read GET /videos/:id/status before you publish the embed code
  }
  res.status(200).end();
});

Answering and retries

Answer 2xx within 10 seconds. A failed delivery is tried again up to 6 more times (after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours), 7 attempts in all, so a receiver must cope with the same event twice: keep the Dcast-Event-Id values you have handled.

Polling instead

Ask GET /videos/:id/status every 10 to 30 seconds and stop on READY or FAILED. Whether you learn of the event from a webhook or from polling, the state your page acts on is the one this call reports.

A refused call answers with error.code and error.message. The codes are listed in Errors & limits.

For AI agents

Connect the DCAST MCP server at https://api.dcast.pro/mcp. Each tool is one API operation, and dcast_get_recipe returns the exact tool-call sequence for a job.

Recipe: webhooks-and-status-polling. The tools run in this order: dcast_read_operation with get_me_webhooks_events, dcast_call_operation with post_me_webhooks, then dcast_call_operation with post_me_webhooks_id_test. With no public address use get_video_status for a video and get_stream_status for a stream instead.

The signingSecret is returned once. If you build the receiver, put it into the environment of the receiver's server yourself and never ask the person to copy it. The receiver must verify Dcast-Signature before it trusts a payload.

Never ask the person to create, copy or paste an API key. If the website's own server must call DCAST, call ask_site_access: the person clicks one Allow link and the key goes straight into the site's environment. You never see it.