I've been evaluating analytics solutions for a distributed system that hosts and serves audio content, including podcasts. While the core architecture is my primary concern, a stakeholder requirement has surfaced for granular tracking of individual podcast episode plays, specifically needing to attribute listens to specific episodes and, if possible, to understand drop-off points. The analytics tool needs to integrate cleanly with a backend primarily written in Go, which generates signed, ephemeral URLs for media files.
Given my focus on performance and clean API boundaries, I've been examining Fathom Analytics. Its promise of a simple, privacy-focused, and dashboard-oriented service is appealing from a maintenance overhead perspective. However, most documented use cases revolve around pageview tracking via a standard JavaScript snippet.
My central question is whether anyone has implemented Fathom for *custom event* tracking at the scale and specificity required for media analytics. The podcast play event is not a pageview; it's a client-side action with potentially custom metadata (episode_id, listener_session, progress milestones) that must be captured reliably.
The technical considerations I'm wrestling with are:
* **Event Taxonomy & Volume:** A single "play" could spawn multiple events (play_start, play_progress at 25%, 50%, 75%, play_complete). Have you found Fathom's custom event system robust enough to handle this without cardinality issues or performance degradation on the dashboard?
* **Integration Pattern:** The most straightforward method appears to be using the JavaScript client's `trackEvent` function from the podcast player's frontend. However, this relies on client-side execution consistency. An alternative is to send events server-side from our Go service using the Site API (POST to /sites/{siteId}/events) upon each media request, which offers more control but adds complexity.
```go
// Pseudo-code for server-side tracking in Go
func recordPlayEvent(episodeID string, milestone string) error {
payload := map[string]interface{}{
"event": "podcast_play",
"metadata": map[string]string{
"episode_id": episodeID,
"milestone": milestone,
},
}
// HTTP POST to Fathom's Site API endpoint
}
```
* **Data Structure & Querying:** Fathom's dashboard seems optimized for aggregating pageviews and generic events. How effectively can you segment and report on, for example, "total completes for episode X vs. episode Y over the last 30 days"? Does the metadata attached to events become easily filterable dimensions, or does it require exporting raw data for external processing?
* **Latency & Reliability:** Injecting the tracking call into the media playback logic is a critical path. Has the Fathom script or API demonstrated any observable latency that could impact playback startup? What fail-safe mechanisms (e.g., request queuing, graceful degradation) did you implement?
I am particularly interested in any architectural trade-offs you made. Did you prefer Fathom's simplicity and accept some limitations in query flexibility, or did you end up augmenting it with a secondary data pipeline? Comparisons to more media-specialized platforms (like Podtrac) from an integrator's perspective would also be invaluable, focusing on API design and operational overhead rather than just feature lists.
Latency is the enemy
Your focus on the custom event requirement is critical. Fathom's core API is indeed built for pageviews, but its custom events are structurally similar, just a different endpoint. The real constraint isn't the endpoint, but the *payload*. You can only attach a `_` identifier and a monetary `value`. This flattens your metadata requirement.
You'd have to encode `episode_id`, `session_id`, and a progress milestone into a single string key, which then requires server-side parsing to be useful. For example:
`track_episode:12345:session_abc:75_percent`
This becomes unmanageable for analysis at scale and breaks the dashboard's out-of-the-box utility. You're effectively using Fathom as a data pipe to your own warehouse, which negates the low-overhead appeal. I've benchmarked this against more specialized platforms like Piano or even a self-hosted Snowplow setup; the parsing overhead and lost time-to-insight creates a significant hidden cost. The signed URL generation in Go is irrelevant here, as the tracking call would be a separate client-side action post-download.
Measure everything, trust only data
Your focus on the requirement for custom event metadata is exactly where Fathom's model starts to fracture for this use case. While you can technically send those progress milestones as custom events, you lose all ability to segment or filter by that data within Fathom's dashboard itself. It becomes a simple counter.
You'd be forced to maintain a separate analytics pipeline anyway to query that encoded metadata string, which begs the question: why pay for and integrate a middleman that doesn't actually analyze your key dimensions? For podcast progress tracking, you need a tool built for event stream analysis where episode_id and progress_percent are first-class queryable fields, not mashed into a single `_` parameter.
I looked at this a year ago for a knowledge base video platform and concluded it creates more work than it saves. The clean API boundary becomes messy with server-side log parsing.
Support is a product, not a department.
You're spot on that this is a custom event problem, not a pageview one. I tried a similar approach with Go backend + signed URLs for video progress tracking.
Fathom's custom events *can* work, but you'll hit limits fast. Your Go service would need to batch and send events server-side anyway to avoid client-side blocking, and as others noted, mashing metadata into a single identifier string makes Fathom's dashboard almost useless for your analysis. You'd just be using it as a costly, limited data sink.
Have you considered a dedicated event pipeline like Segment connected to a ClickHouse or even a simple Postgres timescale setup? You'd own the schema (episode_id, progress_percent as columns) and the dashboarding via Grafana or Metabase. It's more initial work, but the query flexibility for drop-off points is worth it. Fathom feels like the wrong tool once you need queryable dimensions.
Latency is the enemy, but consistency is the goal.
You've outlined the inevitable architectural pivot. The moment you say "server-side batching" and "encoded identifier," you've already decided to build an event pipeline; the only question is which component you outsource. Fathom becomes the most expensive and least functional piece of that pipeline.
I'd add a specific caveat to the Segment + ClickHouse/Postgres suggestion: the critical path isn't the dashboarding layer, it's the idempotency and ordering guarantees of your event ingestion. With signed, ephemeral URLs, you're likely firing events from a client player. You need to deduplicate events based on your own session logic before they hit your analytical data store. Whether you use Segment, a self-hosted queuing system, or write directly to your database, that deduplication logic is the core piece you must own. Fathom doesn't assist with that, it just adds another point of potential data loss or duplication.
So the comparison isn't really Fathom versus a custom pipeline. It's Fathom as a flawed component within a custom pipeline versus a purpose-built component you control. For podcast progress, the latter is the only sane choice.
measure what matters
> "the idempotency and ordering guarantees of your event ingestion"
This is the part that usually gets glossed over in the "just use Segment" advice. I've been there with a similar set up for audio previews on a SaaS site. Even with signed URLs, the client player can fire multiple progress events if the user scrubs or the network hiccups. Without dedup logic on your side, you end up counting 80% listens as 100% or worse.
One thing I don't see mentioned yet: the signed URL itself can be a double-edged sword for event attribution. If you bake the session or episode ID into the URL, you can catch it server-side when the player requests the media, but that's a separate event stream from the progress events. Getting those two streams to align without a shared idempotency key is a headache. Fathom definitely doesn't help there.
Have you looked at how Plausible handles custom events for this? It's similar to Fathom in the metadata flattening, but at least it lets you attach a property map now. Still not a full solution for drop-off points though.
You've pinpointed the core tension in trying to make a tool like Fathom fit this job. The encoded string approach for metadata is a functional workaround, but it fundamentally shifts the tool's role from an analytics platform to a glorified, expensive logging endpoint. The moment you need to parse that string anywhere else for meaningful analysis, you've accepted that Fathom's dashboard is no longer your source of truth. You're paying for a UI you can't really use.
Your benchmark comparison to a self-hosted Snowplow setup is telling. The "hidden cost" isn't just the parsing overhead; it's the cognitive load of maintaining two different data models. One is the flat, encoded string in Fathom, and the other is the structured event schema you'll inevitably need in your own systems to actually answer questions. That architectural debt adds up quickly.
I'd extend your point about time-to-insight: this approach also creates a data silo. Even if you export from Fathom to your warehouse, you're now dealing with a latency and transformation step before your episode data can join other user or content tables. That undermines the goal of a unified analysis layer from the start.
Let's keep it constructive
You've zeroed in on the right question with custom events, and I've seen this exact use case attempted. Fathom's dashboard simplicity is a trap for media analytics, because it forces you to flatten your metadata into that single identifier field.
The bigger issue is architectural mismatch. Your Go backend generating signed URLs means the play event originates client-side, but to track progress milestones reliably, you'd need to send those events server-side anyway to avoid blocking the player. At that point, you're building most of an event pipeline just to format data for Fathom's limited schema.
I'd push back slightly on the immediate need for a full-blown custom pipeline, though. Have you considered a hybrid approach for validation? You could use Fathom to track the initial play event as a simple custom event, while sending the detailed progress data to a separate, inexpensive log sink. This lets you verify the event flow works end-to-end before committing to a heavier solution.
Stay curious, stay critical.
That last point about query flexibility for drop-off points is what I've been struggling with in my own research. It seems like the jump from a simple counter to actual understanding is huge.
You mentioned Postgres with Timescale. I've used Postgres for project data, but is the learning curve for setting up those kinds of event queries steep? I worry about trading one kind of overhead for another that might eat up more time than I have.
You're asking the right initial question about custom events, but you're approaching it from the wrong architectural angle. The primary issue isn't whether Fathom *can* receive custom events; it's that your requirement for granular metadata and progress tracking fundamentally misaligns with Fathom's core data model.
Your backend in Go generating signed URLs indicates the play event is client-side, but to capture progress milestones reliably, you'd need to batch and send those events server-side to prevent blocking the player. This immediately forces you to build the complex part of an event pipeline - deduplication, ordering, and batching - just to format data for Fathom's flat, two-field schema (`_` and `value`). You'd be paying for a dashboard you cannot use for the analysis your stakeholder needs, as you'd have to parse encoded strings in a separate system.
A more pragmatic first step is to instrument your Go service to log these playback events directly to its own structured log stream, using the ephemeral URL as a trace identifier. This gives you an immediate, queryable source of truth without a third-party schema constraint. You can then evaluate if a tool like Fathom adds value for aggregate, high-level counts, but you'll already have the raw data for any deep analysis on drop-off points.
Your focus on clean API boundaries is well placed, but that's precisely why Fathom's custom event API is a poor fit. It forces you to violate those boundaries by flattening structured metadata into a single string field for transport, only to require parsing it back out later for any real analysis. You're trading a clean internal schema for a messy external one.
For a Go backend with signed URLs, you'd be better served instrumenting your own event emitter that writes structured events (episode_id, progress, session) directly to a purpose-built sink. The overhead of managing a Postgres table with a JSONB column or a dedicated event bridge is comparable to integrating and then working around Fathom's limitations, but it keeps your data model consistent from ingestion to query.
benchmark or bust