Podcast downloads
Podcast Downloads counts recorded episode download or playback-start requests, not unique listeners or completed listens.
Podcast Downloads counts recorded episode download or playback-start requests, not unique listeners or completed listens.
Stay in the loop 🚀
The writer reads Signal's /analytics/podcast-downloads/raw endpoint in pages of 1,000 events. It skips events missing an episode ID, podcast ID or timestamp.
For events with userId, the deduplication key is user, episode, podcast, hour and date. Repeated range requests and full-file requests share that key. Anonymous events pass through individually, and the same identified user can count again in another hour.
The legacy hourlyEpisodeCounts response has no user identity. Its fallback synthesizes approximate event times and cannot apply listener deduplication. Check which response format was used when comparing old and current counts. The raw fetch also stops after 100 pages; a limit warning means that hour's data can be incomplete. Event processing.
Summary rows are keyed by tenant, episode, podcast, hour and date. Reprocessing updates matching rows' counts.
A first normal run processes the previous 24 completed hours. Subsequent runs continue after the saved checkpoint through the preceding hour, excluding the current hour. Checkpoint and processing, summary upsert.
An explicit backfill leaves the normal checkpoint unchanged. It upserts returned groups; an empty result skips writing, so it does not clear an existing count for that hour.
The API sums nondeleted summary rows for the tenant and optional episodeId, with inclusive date bounds.
| Result | Date calculation |
|---|---|
| Last 24 hours | Yesterday through today: two calendar dates. |
| Last 7 days | Seven dates before today through today: eight dates. |
| Last 30 days | Thirty dates before today through today: 31 dates. |
| All time | All stored dates through today. |
| Episode table | Both explicit dates, otherwise thirty dates before today through today. |
| Line graph |
Summary cards ignore fromDate and toDate; the episode filter applies to them. The initial line and episode table therefore cover different windows. Set both dates when reconciling those results. .
The writer extracts hours with getUTCHours(), but formats dataDate using the server's local date. The API and frontend also derive “today” separately. Deployed timezone behavior needs verification before explaining a discrepancy around midnight as missing events.
Daily, ISO-week and monthly values sum stored downloads after applying the selected dates. Weeks start Monday. Partial weeks and months include only dates inside the filter; the frontend fills absent points with zero. A zero point can therefore mean no stored row, not a verified absence of requests. .
Episode rows group by episode and podcast. % of total = episode count ÷ matching total × 100, rounded to one decimal place. The denominator uses the same dates and episode filter. Selecting one episode can therefore produce 100% without indicating listening completion. .
Names and publication dates come from RSS metadata. Missing metadata leaves the download count intact and displays Unknown episode or an empty publication date.
The episode picker is built from an unfiltered default-window episode response, not the full episode catalog. An older episode can have historical downloads but be absent from that picker. .
| Symptom | Check and interpretation |
|---|---|
| Recent downloads missing | Compare the last processed hour, latest summary row and Signal event hour. Current-hour activity is intentionally excluded. |
| Counts exceed listener numbers | Compare deduplication keys and missing user identities. One user can count in several hours; anonymous requests remain separate. |
| Cards differ from the line | Compare inclusive card dates with the explicit line dates. Cards retain their fixed windows. |
| Weekly/monthly total looks low | Check whether the selected range cuts off part of the first or last bucket. |
| Episode missing from selector | Check the unfiltered default episode response and historical rows. The picker only uses recently downloaded episodes. |
Export builds a file locally from the rendered line data, including zero-filled points. It is separate from queued member exports. .
The page checks Analytics access; /podcast-download-stats requires READ for DASHBOARD. Check both when the page opens but data fails. .
Source checked at 6cd1850 on 28 September 2026; UI labels inherit the 27 September walkthrough. No new Signal comparison, numeric fixture, export or restricted-role replay was performed. Server timezone, deployed Signal response format and job freshness remain open.
For chart controls, see .
| Hourly graph | Today's stored hours, shown only when the selected range includes today. |
| CSV opens with extra columns | The client joins labels with commas without CSV escaping. Weekly labels contain a comma; this source finding needs export reproduction. |