Troubleshooting · DRM
Streamwake reliability agents

Diagnose DRM failures
without chasing the wrong key.

A working guide to the DRM failure modes Streamwake catches on real license fetches, multi-KID init segments, and per-(browser, CDN, security-level) playback matrices — the five places a DRM stack usually breaks before anyone notices: license-server timeouts, key rotation gaps, certificate and DRM-proxy misconfig, multi-DRM entitlement drift, and POP- or security-level scoped playback breakage.

Protocol: DASH+HLS + DRM
Format: license envelope + pssh / tenc + entitlement
Streamwake probes: license fetch · key rotation · certificate and proxy · multi-DRM entitlement · playback matrix.

Book a technical demo for DRM

Lead magnet
DRM

Read the postmortem — then bring your own incident to Streamwake.

Two ways to engage on this exact failure pattern: book a 30-minute technical demo where we walk through the probe cascade on your source, or hand us an archived incident and watch the agent diagnose it end-to-end.

Both routes land on the scoping intake form — no SDR gate.

Probes

What Streamwake checks

Five families of probes, each with a small, deterministic pass/fail verdict that reads the license envelope and the entitlements matrix directly. Every check has a name — that's the name you'll see on the agents feed.

License fetch
drm.license_reachable
keyserver · proxy
  • Widevine / FairPlay / PlayReady license endpoints each respond with application/octet-stream under 5s.
  • Burst ratio (5xx / total) under 0.05 in a 30s window — burst > 0.05 on the same DRMx path is a regression event.
  • First-byte latency < 3000ms; sustained propagation through the DRM-proxy retains the budget.
Key rotation
drm.key_rotation_window
pssh · tenc
  • Each rotation emits a fresh pssh v1 box within 0.5s of the first sample of the new KID.
  • cenc:default_KID on init.mp4 matches the current KID — stale edge cache on init.mp4 is the smoking gun.
  • Rotation window: packager must emit KID's pssh before msn advance past the rotation boundary by 5s.
Certificate & DRM proxy
drm.cert_valid · drm.proxy_match
cert · proxy-token
  • DRM-proxy cert age under the issuer's rotation interval — cert.stale_age< 24h passes.
  • Proxy-token signing scheme matches the keyserver (v1 vs v2) — proxy_match fails when they diverge.
  • Root cert propagation across all POPs reaches within the rotated-to window (typically 30 minutes).
Multi-DRM entitlement
drm.entitlement_consistent
wv · fp · pr
  • Each key system reports a verdict against the same KID — Widevine, FairPlay, PlayReady should all agree on a supported asset.
  • The security level offered (L1 / L3 / SL2000 / SL3000) matches the device's capability — downgrades show in the verdict detail.
  • Inconsistencies (one DRM refuses, another accepts) flip the probe to fail with the failing key system named.
Playback matrix
drm.playback_matrix
browser · cdn · level
  • Cross-product verdict for browser ∈ {Chrome, Safari, Edge, Firefox}, cdn ∈ all POPs, level ∈{L1, L3, SL2000, SL3000}.
  • Each cell reports 200 / 403 / 503 / timeout — partial-consistent verdicts (some pass, some fail) name the failing cell exactly.
  • Streaming green as long as at least one cell per device is healthy and the agent rebalances away from the failing cell.
Anatomy

Anatomy of a DRM envelope

The five things the agents actually probe: a license HTTP trace, a multi-key init segment with rotation overlap, a multi-DRM entitlement cross-check, a per-(browser, CDN, level) playback matrix, and the agent timeline that ties them together with a rebalance decision.

License fetch (multipart envelope)
POST /widevine/license HTTP/1.1
host: keys.example-cdn.net
content-type: application/octet-stream
x-schema-version: 1.4
x-request-id: cklivedrmlic8127

----- cycle 1 (probe 1/3) -----
HTTP/2 200
content-type: application/octet-stream
server-timing: license-fetch;dur=412, key-derive;dur=88
x-cdn: cdn-A/fra02
x-keyserver-status: ok

----- cycle 2 (probe 2/3) -----
HTTP/2 503
content-type: application/octet-stream
server-timing: license-fetch;dur=512, backend;dur=502
x-cdn: cdn-A/fra02
x-keyserver-status: backend-overloaded
retry-after: 30

----- cycle 2 follow-up (probe 3/3) -----
HTTP/2 200
content-type: application/octet-stream
server-timing: license-fetch;dur=2241, key-derive;dur=104
x-cdn: cdn-B/ams04
x-keyserver-status: ok-from-peering
# rebalance_hints emitted by agent triggered the second-attempt route swap
Multi-key init segment (rotation overlap)
# init.mp4 — atomic multi-KID init segment (60s window)
# tenc box declares KID_A on samples 0..1799
# tenc box (overlap) declares KID_B on samples 1800..3599
# pssh v0 boxes:
#   pssh #1: system=Widevine, kids=[KID_A], provider=streamwake
#   pssh #2: system=Widevine, kids=[KID_A, KID_B], provider=streamwake
# pssh v1 boxes emitted 3s LATE:
#   pssh #3: system=Widevine, kids=[KID_B], provider=streamwake
#     (pssh #3 lands 3s after KID_B's first sample — rotation window missed
#      the boundary by 3 seconds; FairPlay cache ate stale pssh #1)

# edge cache trace:
keys/v1/sub-burst/sess-7421.init.mp4?kid=A      → 200 (cache HIT, age=58s)
keys/v1/sub-burst/sess-7421.init.mp4?kid=B      → 200 (cache MISS, age=0s)
keys/v1/sub-burst/sess-7421.pssh?v=1&kid=B      → 200 (cache MISS, age=0s)
# session-7421's pssh v1 was evicted from edge cache after 60s; the agent
# detects key_rotation_window_miss on the cached age-vs-rotation gap.
Multi-DRM entitlement cross-check (same KID)
{
  "asset_id": "ckdrmasset-march-endgame-7421",
  "kid": "AAAAAAAAAAAAAAAA",
  "checked_at": "2026-08-06T10:42:14.014Z",
  "expectations": {
    "expected_drms": ["WIDEVINE", "FAIRPLAY", "PLAYREADY"],
    "expected_security_levels": {
      "WIDEVINE": ["L1", "L3"],
      "FAIRPLAY": ["L1"],
      "PLAYREADY": ["SL2000", "SL3000"]
    }
  },
  "results": [
    {
      "drm": "WIDEVINE",
      "url": "https://keys.example.com/wv/license",
      "verdict": "pass",
      "level": "L1",
      "key_id": "AAAAAAAAAAAAAAAA",
      "duration_ms": 412,
      "detail": "license fetched, key derived, status 200"
    },
    {
      "drm": "FAIRPLAY",
      "url": "https://fp.licenses.example.com/license",
      "verdict": "fail",
      "level": "L1",
      "key_id": "AAAAAAAAAAAAAAAA",
      "duration_ms": 298,
      "status_code": 403,
      "detail": "KID not in current cert generation; SPDC rotated upstream on 2026-07-22"
    },
    {
      "drm": "PLAYREADY",
      "url": "https://playready.example.com/rightsmanager",
      "verdict": "warn",
      "level": "SL2000",
      "key_id": "AAAAAAAAAAAAAAAA",
      "duration_ms": 1102,
      "detail": "rights issued under SL2000 only — SL3000 challenge unresolved; hardened clients FAIL on SL2000-only entitlement"
    }
  ],
  "summary": {
    "consistent": false,
    "failing_drm": "FAIRPLAY",
    "downgraded_drm": "PLAYREADY"
  }
}
Playback matrix (per-browser, cdn, level)
{
  "stream_id": "ckdrmssafari-cdn-A-7421",
  "scope": "per-(browser,cdn,level)",
  "matrix": [
    { "browser": "Safari 17.5",  "cdn": "cdn-A/eu-west", "level": "L1", "verdict": "fail", "status": 403, "detail": "FP L1 cert chain incomplete on POP cdn-A/eu-west — Apple root cert not yet propagated" },
    { "browser": "Safari 17.5",  "cdn": "cdn-B/eu-west", "level": "L1", "verdict": "pass", "status": 200, "detail": "FP L1 served green on cdn-B/eu-west — POP cert chain current" },
    { "browser": "Safari 17.5",  "cdn": "cdn-A/eu-west", "level": "L3", "verdict": "pass", "status": 200, "detail": "FP L3 (software) is unaffected — cert chain only matters for L1" },
    { "browser": "Chrome 124",   "cdn": "cdn-A/eu-west", "level": "L1", "verdict": "pass", "status": 200, "detail": "Widevine L1 (hardware) succeeds on the same POP — only FairPlay path is broken on cdn-A/eu-west" },
    { "browser": "Chrome 124",   "cdn": "cdn-A/eu-west", "level": "L3", "verdict": "pass", "status": 200, "detail": "Widevine L3 (software) succeeds on cdn-A/eu-west" }
  ],
  "summary": {
    "scope": "browser=Safari(>=17), cdn=cdn-A/eu-west, level=L1",
    "consistent": false
  }
}
Probes → DRM envelope fields
Quick map from probe verdict to the DRM envelope field you should grep.
  • drm.license_reachable content-type: application/octet-stream + server-timing + status
  • drm.key_rotation_window pssh v1 + tenc + cenc:default_KID
  • drm.cert_valid cert.stale_age + cert_seq_no per POP
  • drm.proxy_match proxy_token.sig_scheme vs keyserver.sig_scheme
  • drm.entitlement_consistent → verdict across WIDEVINE · FAIRPLAY · PLAYREADY
  • drm.playback_matrix → cross-product cell failure with (browser, cdn, level) tagged as failing

The license trace is the canonical DRM envelope: application/octet-stream carries the Widevine / FairPlay license protocol, the server-timing header tags license-fetch vs key-derive so the agent can flag the keyserver path (license-fetch) separately from the in-line key work (key-derive). The 503 mid-session burst is the smoking gun for drm.license_reachable flipping to fail.

The multi-key init segment is the place rotation breaks — the box is a single atomic init.mp4 but tenc declares two KIDs over an overlap window, and pssh v1 boxes carry the per-system permissions. When a pssh v1 lands 3s after the KID boundary, drm.key_rotation_window flags the rotation_window_miss — the edge cache chart underneath the box is where it shows up: pssh?kid=B returns MISS while pssh?kid=A&v=1 returns HIT age=58s.

The multi-DRM entitlement cross-check is where drm.entitlement_consistent earns its name: the same KID is probed against Widevine, FairPlay, and PlayReady. On the sample timeline, Widevine returns green at L1, FairPlay denies under a stale SPDC root, and PlayReady downgrades to SL2000. Entitlement_consistent flips to fail because two of the three report different verdicts.

The playback matrix is the per-(browser, CDN, level) view the agent uses to scope a fix. On the sample, Safari L1 on cdn-A/eu-west returns 403; cdn-B/eu-west serves the same Safari L1 green. The cell-level verdict means there is a whole cohort (Safari L1 on cdn-A) that needs an alternate path — the agent emits pin_safari_L1_to_cdn_B to rebalance them.

Why rebalance beats alert-only
The agent emits rebalance hints, not just pages.

The DRM failure surface is too noisy for alert-only — too many POPs, too many browsers, too many security levels. Streamwake names the failing cell AND emits a rebalance hint that routes traffic around the failing cell until a real fix is shipped. Streaming stays green while engineering works the issue; most failures are remediated before the on-call engineer finishes reading the page.

Failure modes

Ten ways a DRM stack breaks

Each row: the symptom the player reports → the underlying cause → a fix that holds under the next probe cycle → the rebalance hint the Streamwake agent emits. The rebalance hint is the part the agent does on its own — it routes traffic around the failing cell while engineering works the actual fix.

01License server returns 5xx mid-session — keyserver burst overload
failure mode
Symptom

Player fetches the license fine for the first minute, then a 503 (or 502) burst rolls in from the keyserver. The error envelope comes back as application/octet-stream (Widevine / FairPlay), not a clean CDN MISS — it's the keyserver, not the edge.

Diagnosis

License-server-side burst — the keyserver pool is hot or pinned to a degraded replica, and the burst ratio (5xx_responses / total_responses in a 30s window) crossed the probe threshold. drm.license_reachable flips to fail when burst_ratio > 0.05 even if the prior probe was green.

Fix

Pre-warm the keyserver pool, raise the license-fetch burst headroom, and front the keyserver with a CDN that has a healthier peering topology for the keyserver region. Verify drm.license_reachable flips to pass and burst_ratio returns under 0.02 for 10 consecutive cycles.

Streamwake rebalance

Agent detects license_5xx_burst_ratio > 0.05 on cdn-A/fra02 and emits rebalance_hints=[{to: cdn-B, scope: 'level:L1', browser: '*'}] after two bad cycles. cdn-B/ams04 (which proxies the same keyserver through a healthier peering path) absorbs the next five license fetches.

02License fetch times out behind a slow DRM-proxy — first-byte latency EXPLODES
failure mode
Symptom

Player.attach stalls at the EME step even though the manifest is serving green. The browser console shows "license-fetch timed out at 6000ms" and the first byte never arrives. Network probe is fine — it is the DRM-proxy path that is slow.

Diagnosis

The DRM-proxy layer in front of the keyserver is consuming the entire budget on first-byte: TLS handshake succeeds, but the proxy does not forward the request until it has verified the proxy-token signature. drm.proxy_match returns warn when the signing-key version on the proxy doesn't match the issuer's v2 scheme.

Fix

Re-issue the proxy-token under the current signing scheme (v2) and increase the proxy forward-headroom budget so first-byte latency stays under 3000ms. After re-issue, drm.proxy_match flips back to pass and the finish-to-start gap on drm.license_reachable drops below 1.5s.

Streamwake rebalance

Agent detects proxy_token_sig_age > 24h and emits rebalance_hints=[renew_proxy_token]. On the next cycle the proxy-token is re-issued under the current signing scheme and drm.proxy_match returns pass; license-fetch first-byte latency drops from 5,820ms to 1,140ms.

03Key rotation window misses a pssh boundary — clients cache stale KID
failure mode
Symptom

Two SSDs with overlapping keys ship, but FairPlay clients still get KID_A on KID_B samples for the first 3–5 seconds after rotation. Errors read "KID mismatch: expected KID_B, manifest returned KID_A".

Diagnosis

The packager emits pssh v1 box 3s late after the KID_B first sample. The edge cache still holds pssh v1 under the original key (KID_A), so the first wave of clients post-rotation reads a stale manifest and trips KID_B entitlement on what looks like a KID_A asset.

Fix

Align the packager pssh emission with the KID boundary (pssh v1 must land within 0.5s of the first KID_B sample, before the next msn rollover). Pre-warm the edge cache on KID_B pssh so first-cycle reads manifest the right generation.

Streamwake rebalance

Agent detects rotation_lag_s > 2.5 on the KID_B pssh and emits rebalance_hints=[prewarm_edge_cache_for_new_kid]. The next probe witnesses KID_B pssh served from the warmed POP, and drm.key_rotation_window returns to pass for one cycle.

04Cached cenc:default_KID blocks rotation — clients keep picking the old KID
failure mode
Symptom

After rotation, clients still resolve "default_KID=KID_A" even though cenc:default_KID was rewritten to KID_B in the new init segment. The browser skip-keys-after-KID-mismatch path triggers a hard re-key — usually a stall.

Diagnosis

Edge / proxy cache is holding init.mp4 under the previous rotation tag (the SHA of init.mp4 is unchanged because tenc boxes overlap, but cenc:default_KID is different). Cache hit on the previous init.mp4 means the player reads the previous default_KID and queries KID_A on KID_B samples.

Fix

Strip an explicit cache-key from init.mp4 that includes cenc:default_KID (or rotate the init.mp4 SHA on each tenc change via an outgoing edge policy). Pair the cenc change with a soft purge so the next read is forced to the current init.

Streamwake rebalance

Agent detects init_seg_cache_age > rotation_window and emits rebalance_hints=[soft_purge_init_seg_for_kid_B]. The next probe sees init.mp4 served from the new cache-key, default_KID resolves to KID_B, and drm.key_rotation_window flips to pass.

05DRM proxy serves a stale cert after rollover — keys refused silently
failure mode
Symptom

After a cert rollover (the keyserver's cert rotates), some players still handshake-validate the keyserver against the previous cert. Console reads 'keys refused — cert expired' or 'cert chain incomplete'. The probe sees a 200 on the first request and then fails on the second.

Diagnosis

DRM-proxy cert was rolled at the issuer but the proxy pair wasn't re-synced — so it serves both old and new certs depending on the POP. drm.cert_valid fails because cert.stale_age > 24h — the SP/Browser pool is half old-root, half new-root.

Fix

Re-sync the proxy pair and front-load the new cert on every POP. Bump the cert-stale-age threshold above the issuer rotation interval so legitimate rotations do not flag as failures — only true proxy missyncs flip the probe to fail.

Streamwake rebalance

Agent detects cert.stale_age > 24h and triggers a DRM-proxy token renewal task before the next probe cycle. Once renewed, drm.cert_valid returns pass and the cert_seq_no on the proxy serves both old and new root chains until the next soft-rotation.

06DRM-proxy token signature mismatched — proxy fails to forward
failure mode
Symptom

License fetch hangs at the proxy boundary — first-byte latency is unbounded. DevTools reports the request 'pending' until it times out at the client. No 4xx, no 5xx — the proxy never responds.

Diagnosis

The DRM-proxy was issued a token under signing scheme v1, but the keyserver has been moved under scheme v2. The proxy accepts the request (token signature validates under v1) but the keyserver v2 handshake on the proxy-target side fails, leaving the request stuck in a forwarding queue.

Fix

Re-issue the proxy-token under scheme v2 — both sides now agree. Set up a dual-scheme period so the proxy can decode both v1 and v2 tokens while the issuer rolls forward.

Streamwake rebalance

Agent detects proxy_token.scheme_v != keyserver.scheme_v and emits rebalance_hints=[renew_proxy_token + dual_sign_for_24h]. The next probe witnesses the proxy-token under v2 and drm.proxy_match returns pass.

07Widevine entitlement OK, FairPlay denies the same KID — multi-DRM drift
failure mode
Symptom

Same content, same asset, same KID — Chrome plays cleanly with a Widevine license, but Safari throws 'license refused'. Player reports show Widevine at L1 pass, FairPlay at L1 fail on a 403.

Diagnosis

SPDC cert rotation on the FairPlay endpoint landed upstream but the enterprise FairPlay deployment did not re-issue the new root to its Apple-side licensing endpoint. Widevine and PlayReady entitlement is consistent — only FairPlay flips to fail because its cert chain is stale.

Fix

Refresh the FairPlay cert chain on the Apple-side endpoint AND re-issue the asset under the current SPDC root. drm.entitlement_consistent flips to pass when all three key systems return consistent verdicts on the same KID.

Streamwake rebalance

Agent detects entitlement_inconsistent on FairPlay and emits rebalance_hints=[pin_fairplay_to_cdn_B + refresh_spdc_root]. cdn-B/eu-west serves FairPlay L1 from a current root while the upstream re-syncs — playback recovers for ~80% of the Safari cohort.

08PlayReady session-token wrong for the device class — SL2000 vs SL3000
failure mode
Symptom

Hardened clients (PlayReady SL3000) fail on a license that only offers SL2000. Browser logs read "Session token missing hardening proof." Standard clients succeed. Entitlement looks green until the hardened cohort is dissected separately.

Diagnosis

PlayReady rights manager is configured to issue SL2000 entitlements only (the lower-tier default). Hardened clients that require SL3000 receive a challenge because the issuer never offers it. drm.entitlement_consistent warns — standard clients are fine, hardened clients are downgraded.

Fix

Raise the rights manager to issue SL3000 entitlements where the device supports it AND verify the hardened client can satisfy the SL3000 challenge. Pair this with the SL2000 fallback so legacy clients are not broken during the rollout.

Streamwake rebalance

Agent detects playready_level_downgrade_count > 0 and emits rebalance_hints=[raise_rights_to_sl3000_for_hardened_clients]. On the next probe hardened clients receive SL3000 entitlements and the downgrade count drops to zero within 5 cycles.

09FairPlay playback fails on Safari + specific CDN POP — partial POP miss
failure mode
Symptom

FairPlay L1 playback fails for Safari clients routed through cdn-A/eu-west but succeeds for Safari clients routed through cdn-B/eu-west. Same manifest, same KID, same client build — only the POP changes.

Diagnosis

POP-level cert-chain miss on cdn-A/eu-west (Apple root not yet propagated to that POP). drm.playback_matrix reports partial-consistent verdict mix: 4 pass, 1 fail. The failing cell is exactly browser=Safari(>=17), cdn=cdn-A/eu-west, level=L1.

Fix

Roll the Apple root cert to cdn-A/eu-west via the CDN provider and pin the manifest to prefer cdn-B for Safari L1 until cdn-A is repaired. Verify drm.playback_matrix returns consistent after the cert is propagated to the failing POP.

Streamwake rebalance

Agent detects playback_matrix inconsistent on safari+cdn-A/L1 and emits rebalance_hints=[pin_safari_L1_to_cdn_B_for_eu-west]. cdn-B absorbs the next five Safari L1 license fetches; cdn-A gets repaired by the CDN provider while streaming stays green.

10DRM L1 hardware fails on Chrome, L3 software passes — security-level downgrade
failure mode
Symptom

Chrome on a desktop with a TEE/HDCP path throws "hardware DRM unavailable" and falls back to L3 (software). The fallback plays, but the security level drops from L1 to L3 and the player logs "DRM level: SL3000 unreachable".

Diagnosis

Chrome's hardware path uses the platform's secure decoder; the device's TEE chain is reporting an incomplete attestation chain, so the Chrome CDM rejects L1 device certificates. drm.playback_matrix reports level=L1 / fail, level=L3 / pass; the agent logs level_downgrade_count>0.

Fix

Re-flash the TEE / device-cert chain so the attestation is current. Pair the fix with a known-good CDM build (current Chrome CDM) — older CDM builds may trip platform-level attestation checks that the current CDM already works around.

Streamwake rebalance

Agent detects chrome_L1_attestation_failures > threshold and emits rebalance_hints=[downgrade_chrome_to_L3_for_device_class + alert_cdm_team]. Chrome L3 (software) is acceptable short-term; the agent also surfaces a CDN tag for the incident so on-call gets paged the same cycle.

Diagnose

Diagnose with Streamwake

Register the DRM source against POST /api/v1/streams, then read the agent timeline back through GET /api/v1/agents. The probe verdicts in the timeline are exactly the rows above — license fetch, key rotation, certificate and proxy, multi-DRM entitlement, playback matrix. The agent.rebalance_hints block names the rebalance decisions the agent emitted.

The curl below registers a DRM source URL with a 30-second probe cadence. The DASH+DRM protocol routing makes the agent run license / key-rotation / certificate / proxy / entitlement / playback-matrix probes on every refresh — and the agent timeline shows each verdict plus the rebalance hints the agent emits when a probe fails.

The cookie is the same better-auth.session_token that gates every /api/v1/* call — see the auth guide for how to mint one.

POST /api/v1/streams
curl -X POST https://streamwake.polsia.io/api/v1/streams \
  -H "content-type: application/json" \
  -b "better-auth.session_token=<your-session-cookie>" \
  -d '{
    "sourceUrl": "https://cdn.example.com/live/event/manifest.m3u8",
    "protocol": "DASH+DRM",
    "probeIntervalSeconds": 30,
    "agents": [
      "drm.license_reachable",
      "drm.key_rotation_window",
      "drm.cert_valid",
      "drm.proxy_match",
      "drm.entitlement_consistent",
      "drm.playback_matrix"
    ]
  }'
GET /api/v1/agents?stream_id=…
curl https://streamwake.polsia.io/api/v1/agents?stream_id=<id> \
  -b "better-auth.session_token=<your-session-cookie>"
Agent timeline response (trimmed)
{
  "stream_id": "ckdrmtroubleshoot-7421",
  "source": "https://cdn.example.com/live/event/manifest.m3u8",
  "protocol": "DASH+DRM",
  "checked_at": "2026-08-06T10:42:14.014Z",
  "checks": [
    {
      "probe": "drm.license_reachable",
      "result": "fail",
      "endpoints": [
        { "drm": "WIDEVINE",  "url": "https://keys.example.com/wv/license",         "verdict": "pass", "status": 200 },
        { "drm": "FAIRPLAY",   "url": "https://fp.licenses.example.com/license",     "verdict": "fail", "status": 403 },
        { "drm": "PLAYREADY",  "url": "https://playready.example.com/rightsmanager", "verdict": "warn", "status": 200 }
      ],
      "detail": "FAIRPLAY 403 on cdn-A/eu-west; agent rebalanced to cdn-B/eu-west on the follow-up probe and WIDEVINE returned green"
    },
    {
      "probe": "drm.key_rotation_window",
      "result": "warn",
      "key_count": 2,
      "rotation_lag_s": 3.1,
      "detail": "KID_B's `pssh` v1 box landed 3.1s late — rotation_window_miss reported once, stable since"
    },
    {
      "probe": "drm.cert_valid",
      "result": "fail",
      "cert_age_h": 27.4,
      "detail": "DRM proxy cert last rotated 27.4h ago; cert.stale_age above 24h threshold"
    },
    {
      "probe": "drm.proxy_match",
      "result": "warn",
      "sig_age_h": 26.8,
      "detail": "DRM-proxy token signature v1 no longer matches the issuer's signing scheme v2; re-issue required before next probe cycle"
    },
    {
      "probe": "drm.entitlement_consistent",
      "result": "fail",
      "consistent": false,
      "failing_drm": "FAIRPLAY",
      "detail": "Widevine and PlayReady report consistent entitlement for KID=AAAAAAAAAAAAAAAA; FairPlay denies under SPDC root rev=2026-07-22"
    },
    {
      "probe": "drm.playback_matrix",
      "result": "fail",
      "matrix_consistent": false,
      "scope": "browser=Safari(>=17), cdn=cdn-A/eu-west, level=L1",
      "detail": "per-(browser,cdn,level) verdict mix: 4 pass, 1 fail. Specific regression: Safari L1 on cdn-A/eu-west returned 403"
    }
  ],
  "agent_rebalance_hints": [
    "pin_to_cdn_B_for_level_L1",
    "renew_proxy_token"
  ],
  "summary": {
    "first_failing_probe": "drm.license_reachable",
    "fallback_path": "cdn-B/eu-west (FairPlay L1 verified 2026-08-06T10:42:13)",
    "license_outage_minutes": 4
  }
}
Next step

Want Streamwake to catch this on its own?

Sign up, register a DRM probe, and the same license / key rotation / certificate / proxy / entitlement / playback-matrix probes that produced the timeline above run on every refresh — and surface in a Slack channel, a webhook, or the streams dashboard.

Need Streamwake on one of your incidents?
Would you like Streamwake to analyze one of your historical incidents and show where AI could reduce investigation time? (Filed under: DRM.)
Incident analysis
  • Pick a recent on-call incident — manifest stall, edge miss, player-side stall, or peer congestion.
  • We replay it through the same reliability-agent probe cascade used on the postmortem above.
  • You walk away with a written what-could-have-been-Automated readout, not a sales deck.
Back to library

Read the next protocol guide

The other live entries cover the canonical protocol-by-protocol failure patterns.