Why event-driven matters in live video
Live streaming is a real-time system: encoders connect, renditions spin up, segments are written, viewers spike, networks flap, and streams end—often with little warning. If your operations and tooling rely on polling a live stream API every 30–60 seconds, you either react too late or hammer APIs for no reason. Streaming webhooks flip that model: your infrastructure pushes events to you the moment they happen so you can automate responses immediately.
This post dives into event-driven video workflows: what events to emit, how to validate and scale webhook delivery, patterns that keep stream automation reliable, and tradeoffs vs. polling. Well also outline practical architectures that connect ingest/transcode/packaging to the rest of your stack (NOC, CMS, ads, billing, chat, and more).
Note: Both Nighthawk Server (self-hosted) and Nighthawk Cloud expose streaming webhooks and a REST live stream API so you can wire live events into your systems without resorting to brittle polling.
A taxonomy of streaming events
Start by deciding which domain events your platform will publish. Typical categories:
- Session lifecycle
- stream.created (scheduled or provisioned before ingest)
- stream.started (encoder connected; first keyframe received)
- stream.updated (bitrate, average FPS, codecs changed, or metadata updated)
- stream.ended (encoder disconnected, timeout reached, or stop via API)
- Ingest and health
- ingest.retry (RTMP reconnect, SRT stream ID switched, RTSP re-init)
- ingest.error (auth failed, bad key, unsupported codec, no keyframes)
- srt.latency.updated (e.g., caller requested 120 ms, measured 160 ms)
- rtmp.dropped.frames (threshold exceeded; notify at, say, >2% over 60 s)
- Transcode/ABR
- transcode.rendition.up/down (e.g., 1080p ladder enabled/disabled)
- transcode.error (GPU overload, encoder crash, incompatible profile)
- thumbnail.generated (periodic or on-demand poster updates)
- Packaging and delivery
- hls.variant.started/ended (playlist became available/unavailable)
- dvr.segment.written (HLS segment indexed to recording/DVR)
- origin.cache.miss (edge-to-origin ratio anomaly)
- Compliance and monetization
- scte35.signal (DAI opportunity; splice insert or time signal)
- drm.key.rotated (if doing key rotation for low-latency or high-security)
- captions.added (CEA-608/708 or WebVTT ingestion detected)
- Audience and interaction
- viewer.threshold.crossed (concurrent viewers hit 5k, 10k, etc.)
- webrtc.client.joined/left (if using real-time WebRTC interactivity)
- Infrastructure and cluster
- node.capacity.warning (CPU/GPU/bandwidth thresholds)
- edge.failover (primary origin failed, backup serving)
You wont need all of these, but explicitly naming the ones you do need makes automation discoverable and testable.
Example webhook payloads
Keep payloads compact, stable, and versioned. Use ISO-8601 timestamps and include an id for idempotency. Example (illustrative only):
{
"id": "evt_01HZY0K9GZP6X0",
"type": "stream.started",
"specversion": "1.0",
"source": "ingest/rtmp",
"occurred_at": "2026-08-31T14:22:11Z",
"stream": {
"id": "strm_9j2k4",
"app": "live",
"stream_key": "pub-1234",
"protocol": "rtmp",
"codec": { "video": "h264", "audio": "aac" },
"renditions": ["1080p", "720p", "480p"],
"low_latency": false
},
"ingest": {
"remote_ip": "203.0.113.45",
"port": 1935,
"srt": { "latency_ms": null, "mode": null }
},
"metrics": {
"bitrate_kbps": 4200,
"fps": 30
}
}
For sensitive events, add minimal context and fetch details later using your live stream API to avoid leaking secrets through third-party tools.
Delivery semantics, retries, and idempotency
A webhook system is part of your control plane. Design it like a queue, not just an HTTP POST firehose.
- At-least-once delivery: Assume duplicates can happen. Provide an id and retry-count headers so receivers de-duplicate.
- Timeouts: Typical 25 seconds per delivery attempt; dont block the sender. If your handler may take longer, offload to a queue worker.
- Retries with backoff and jitter: e.g., retry after 5s, 30s, 2m, 10m with +/- 20% jitter. Cap total retry window (often 24 hours) to avoid infinite storms.
- Ordering: Dont assume strict ordering across retries. If ordering matters, include sequence numbers per stream or build conflict resolution in handlers.
- Idempotency key: Use the event id as a natural idempotency key.
Example receiver logic (Node.js/TypeScript pseudocode):
import crypto from "crypto";
import express from "express";
import { createClient } from "redis";
const app = express();
app.use(express.raw({ type: "application/json" })); // preserve body for HMAC
const redis = createClient();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;
function verifySignature(req: any) {
const sigHeader = req.header("X-Webhook-Signature");
const ts = req.header("X-Webhook-Timestamp");
if (!sigHeader || !ts) return false;
const msg = `${ts}.${req.body.toString("utf8")}`;
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(msg)
.digest("hex");
// Use constant-time comparison
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sigHeader));
}
app.post("/webhooks/stream", async (req, res) => {
if (!verifySignature(req)) return res.status(400).send("bad signature");
const evt = JSON.parse(req.body.toString("utf8"));
const key = `evt:${evt.id}`;
const set = await redis.set(key, "1", { NX: true, EX: 24 * 3600 });
if (!set) return res.status(200).send("duplicate"); // already processed
// Fast-ack; enqueue for async processing
await redis.lPush("work:stream-events", JSON.stringify(evt));
return res.status(202).send("queued");
});
app.listen(8080);
Key points:
- Use the raw request body for HMAC verification.
- Include a timestamp header and reject requests older than, say, 5 minutes to prevent replay.
- Persist a short-lived idempotency record (Redis SETNX shown) to handle duplicates.
- Acknowledge quickly; do heavy work in background workers.
Security hardening for streaming webhooks
- HMAC signatures: Sign timestamp + body with SHA-256. Rotate secrets periodically.
- TLS and mTLS: Always over HTTPS; optionally require mutual TLS for intra-DC traffic.
- IP allowlists: For self-hosted servers, restrict egress and ingress. Note that cloud IPs can change; prefer signed webhooks over IP-based trust.
- Replay protection: Enforce timestamp windows and single-use delivery ids.
- Least privilege: Keep webhook payloads minimal; fetch expanded details via your live stream API using service credentials.
Patterns for event-driven video workflows
1) Direct delivery to microservices
- The streaming platform POSTs to your service URLs (auth, CMS, overlays, notifications).
- Pros: Simple; minimal moving parts; low latency.
- Cons: Each service must scale with burst traffic; failure domains are coupled.
2) Fan-in with a durable queue
- Terminate all incoming webhooks at a gateway, verify signatures, then enqueue (Kafka, SQS, RabbitMQ, NATS JetStream).
- Workers consume and perform actions (update CMS, trigger transcoders, send alerts).
- Pros: Decouples producers/consumers; handles bursty traffic; supports replays.
- Cons: More infra to operate; exactly-once still requires idempotency at consumers.
3) Functions-as-a-service
- Route webhooks to cloud functions (AWS Lambda, Cloud Functions, Cloud Run) behind an API gateway.
- Pros: Scales to zero; great for spiky events (e.g., stream.start every morning).
- Cons: Cold starts; strict timeouts (often 14 minutes) may require splitting work.
4) Event bus with rules
- Transform webhook events into a normalized bus (CloudEvents) and republish to SNS/EventBridge/PubSub for team-specific automations.
- Pros: Multiple teams subscribe without reconfiguring the streaming platform.
- Cons: Requires governance to prevent event sprawl.
5) Edge-aware actions
- For origin-edge clusters, ship per-node events to a regional aggregator to avoid cross-region chatter. Apply local mitigations (e.g., edge failover) before notifying global systems.
Real automations that pay for themselves
- Auto-start/stop pipelines: Spin up GPU transcoders only when stream.started arrives; scale down on stream.ended after DVR flush (often saves 3060% compute on intermittent channels).
- Health-based slate insertion: On ingest.error or rtmp.dropped.frames, switch to a slate loop and notify producers via Slack/SMS. Remove slate when ingest health recovers.
- Dynamic overlays: On viewer.threshold.crossed, call your graphics service to add LIVE or sponsor bugs via REST and web socket overlays.
- DVR indexing and clipping: On dvr.segment.written, incrementally update an index to support near-real-time replay. Trigger clip extraction when scte35.signal arrives.
- Compliance: When captions.added fires, update player UI states; alert if captions missing after N seconds.
- Billing/quotas: Increment usage meters on stream.started/ended and hls.variant.started/ended instead of sampling.
Webhooks vs. polling vs. streams: a quick comparison
| Approach | Pros | Cons | Typical fit |
|---|---|---|---|
| Webhooks (push HTTP) | Immediate, low overhead, simple to adopt | Requires public endpoint, retries/validation, at-least-once semantics | Most stream automation; NOC alerts; autoscaling |
| Polling a live stream API | Easy to prototype, no inbound exposure | Latency tied to interval; API load; risk of misses during spikes | Legacy systems; dashboards without push support |
| Event bus (SNS/SQS/Kafka/EventBridge) | Fan-out to many consumers; replay; filtering | More infra and governance | Larger orgs; multi-team workflows |
| WebSocket/SSE subscriptions | Sub-second updates | Persistent connections; stateful servers | Operator consoles; chat/mod tools |
The practical path for most teams: start with streaming webhooks for core signals, then normalize into a queue or bus as you scale.
Observability and SLOs
Treat webhook delivery as a product with its own SLOs.
- Metrics to track: delivery latency p50/p95, success rate per endpoint, retry rate, event backlog depth, consumer lag.
- Tracing: Include a correlation id (e.g., X-Request-Id) across ingest logs, transcode logs, and webhook deliveries.
- Dead-letter policies: Route permanently failing events to a DLQ with retention for forensic replay.
- Replay tooling: Allow secure re-delivery by event id and time range to dev/staging.
Schema versioning and evolution
- Version in the payload (e.g., specversion) or path (/v1/webhooks).
- Additive changes only (new fields) whenever possible. For breaking changes, bump version and run both for a transition period.
- Publish a JSON Schema or OpenAPI component; validate payloads server-side in CI to prevent drift.
Integrating with ads (SCTE-35) and monetization
Ad markers arrive as binary splices or base64 in HLS EXT-X-DATERANGE tags. Your event-driven workflow can:
- Pass through scte35.signal from ingest to ad decisioning.
- Trigger server-side ad insertion or signal your CDN to swap slates.
- Record ad windows in VOD manifests for downstream analytics.
Tip: Normalize SCTE-35 into a canonical event with fields like type, pts_time, duration, and segmentation descriptors so downstream consumers dont need to parse binary.
Handling bursty traffic
Viewer spikes and encoder flaps can generate hundreds to thousands of events per minute per region in busy systems.
- Back-pressure: If the receiver returns 429/503, pause and increase backoff.
- Batching: For low-priority analytics, batch events server-side (e.g., 100 events or 1 second) before delivery.
- Partitioning: Partition by stream id in Kafka so related events preserve order where it matters.
Self-hosted vs. managed platforms for webhooks
- Self-hosted servers (on-prem or your cloud): Maximum control; run sidecars that emit events close to the pipeline; can require mTLS and private peering. Youll own scaling and upgrades. If you prefer perpetual licensing and Docker-native deployments, evaluate products like Nighthawk Server.
- Managed platforms: Faster to adopt; global reach out of the box; webhook delivery handled for you. Typically expose streaming webhooks and a live stream API for fetching details. Vendors like Mux expose robust webhook catalogs; AWS IVS leans on EventBridge for channel events; other engines (e.g., Ant Media, Wowza via server modules/callbacks) support hooks or extensibility layers. Differences often lie in event breadth, delivery guarantees, and tooling.
Choose based on compliance, where your traffic lives, and whether you want to operate origins yourself.
A practical implementation checklist
- Define event catalog and ownership per event.
- Implement HMAC signatures, timestamp windows, and idempotency.
- Provide delivery logs, retries with jitter, and DLQ.
- Normalize into an internal bus/queue; emit CloudEvents where possible.
- Document payloads with examples and JSON Schema.
- Offer test mode and replay tooling (per event id/time window).
- Monitor SLOs and set pager thresholds on delivery success and latency.
Putting it all together: an example automation flow
- Encoder goes live (RTMP/SRT). Platform emits stream.started.
- Gateway verifies signature and enqueues event.
- Worker A updates channel state in CMS; Worker B requests dynamic overlays via the graphics service; Worker C scales transcode nodes based on expected ladder.
- As ABR variants start, hls.variant.started events make the player go-live control active in your frontend via WebSocket.
- During a drop in upstream bitrate causing rendition downshift, transcode.rendition.down triggers an internal alert; slate insertion workflow waits for a sustained threshold before acting to avoid flapping.
- When the stream ends and DVR is flushed, stream.ended triggers archive packaging and storage lifecycle rules (e.g., move to infrequent access after 30 days).
Latency from ingest to automation is typically on the order of a few hundred milliseconds to a couple of seconds, dominated by your queue and worker cold starts.
Where Nighthawk fits
If youre building or upgrading event driven video workflows, Nighthawk gives you options:
- Nighthawk Server: self-hosted streaming with a perpetual license (buy once, own forever). Docker-native with RTMP/SRT/RTSP ingest, HLS/WebRTC delivery, ABR transcoding, recording/DVR, origin-edge clustering, REST API, and streaming webhooks for automation.
- Nighthawk Cloud: fully managed platform with pay-as-you-go that includes webhook delivery and a live stream API for details and control.
Both support the core primitives discussed above so you can connect your operations stack, whether you prefer to run your own servers or not.
Try it yourself
Looking to wire streaming webhooks into your stack without renting software you host yourself? Explore Nighthawk Servers perpetual-license model or start free on Nighthawk Cloud. See docs, pricing, and examples at https://nighthawk.tv
---
Frequently asked questions
Whats the difference between streaming webhooks and polling a live stream API?
Webhooks push events to your endpoint as they happen, which reduces latency and API load. Polling queries the API on a schedule, which can miss brief state changes or produce noisy checks. Many teams start with webhooks for core triggers and use the API for on-demand details.
How do I ensure webhook deliveries are secure and authentic?
Use HMAC signatures over timestamp + raw body, validate within a short window (e.g., 5 minutes), require HTTPS (and optionally mTLS), rotate secrets, and implement idempotency using the event id.
Whats a good retry policy for webhook delivery?
Aim for at-least-once delivery with exponential backoff and jitter (e.g., 5s, 30s, 2m, 10m, up to 24h). Cap maximum attempts, and route permanently failing events to a dead-letter queue for analysis and replay.
Which events should I start with for stream automation?
Begin with stream.started, stream.ended, ingest.error, transcode.rendition.up/down, dvr.segment.written, and scte35.signal if you do ad insertion. Expand as your operational maturity grows.