benecaster_feed_enclosure_url
Filters the audio or video URL placed in the RSS enclosure tag for each episode. Use to route downloads through a custom proxy, or append tracking parameters where you need $show_id. Fires after benecaster_episode_audio_url — prefer that hook for URL substitution; use this one for proxy-routing or parameter-appending.
Core registers two callbacks at priority 10. Analytics\ProxyUrlRewriter swaps in the download proxy (/benecaster-download/{id}/?token={TOKEN}) on non-public tiers when download tracking is on for the episode. Feed\EnclosureUrlPrefixer joins the show’s Enclosure URL prefix (Show Settings → Feeds), dropping the URL’s leading https:// and keeping http://, and passes an already-proxied URL through untouched. Their result does not depend on which runs first: if the prefixer runs first, the rewriter replaces the prefixed URL wholesale with the proxy URL; if the rewriter runs first, the prefixer recognises the result as a proxy URL and leaves it alone. A callback registered at priority > 10 sees the final URL — prefixed, proxied, or both in whichever combination core produced.
⚠ Never wrap a proxy URL in a third-party prefix. Its ?token={TOKEN} is the subscriber’s private-feed credential; wrapping it would put that token in the tracking service’s own logs. Check for home_url( '/benecaster-download/' ) before prefixing, the way core’s own prefixer does.
The same value is also accessible through benecaster_feed_episode_data as the enclosure_url key — use this dedicated filter when you only need the URL and want to avoid receiving the full episode data array. The returned URL must always be a valid, non-empty URL.
It fires in two places, not one. Per item, where the result goes into enclosure url="..."; and again when building each channel-level <podcast:trailer>, with that trailer episode’s ID. Both of core’s own callbacks run in both places.
Two practical consequences. A prefix or proxy sees the trailer URL rewritten too, which is usually what you want. And a callback that counts its own invocations fires once per item plus once per trailer, not once per item.
For routing every enclosure through a measurement or CDN prefix service (Podtrac, OP3, Podscribe), use Show Settings → Feeds → Enclosure URL prefix instead of this filter — no code needed for that case.
Count feed downloads with Podtrac, OP3 or Podscribe
No code needed for the common case. Open Show Settings → Feeds and paste the service’s prefix into Enclosure URL prefix — e.g. https://dts.podtrac.com/redirect.mp3/, https://op3.dev/e/, or your Podscribe prefix. Core (Feed\EnclosureUrlPrefixer, priority 10) then joins it onto every enclosure, in the public feed and every tier, and onto <podcast:trailer>; drops the episode URL’s leading https:// (as Podtrac and OP3 document) and keeps http://; never wraps the download proxy’s /benecaster-download/…?token=… URL — the proxy’s 302 carries the prefix instead, so the service counts the download without ever seeing the token; skips a URL that already starts with the same prefix; and clears the show’s feed cache when the setting changes.
This code example is for the advanced case: chaining a second service. One prefix per show is stored, so a second service needs its own callback at priority > 10, after core, copying core’s two rules — leave a proxy URL alone, and drop https://. One limit to know: this callback only reaches feed URLs. When download tracking is on, the proxy’s redirect carries only the show’s stored prefix, so downloads through the proxy are counted by the service in the setting and not by the chained one. If both services must count every download, put the one that matters most in the setting.
<?php
add_filter( 'benecaster_feed_enclosure_url', function (
string $url,
int $episode_id,
int $show_id,
string $tier_slug
): string {
$outer = 'https://op3.dev/e/';
if ( '' === $url || str_starts_with( $url, $outer ) ) {
return $url;
}
// Never wrap the download proxy URL: it carries the subscriber's feed token.
if ( str_starts_with( $url, home_url( '/benecaster-download/' ) ) ) {
return $url;
}
return $outer . preg_replace( '#^https://#i', '', $url );
}, 20, 4 );
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
$url |
string |
— | The enclosure URL |
$episode_id |
int |
— | ID of the episode |
$show_id |
int |
— | ID of the show |
$tier_slug |
string |
— | Tier slug for this feed compilation pass |
Returns:
string
Examples
Append UTM source parameter to track feed traffic
add_filter( 'benecaster_feed_enclosure_url', function ( string $url, int $episode_id, int $show_id, string $tier_slug ): string {
if ( str_starts_with( $url, home_url( '/benecaster-download/' ) ) ) {
return $url;
}
return add_query_arg( 'utm_source', 'rss_feed', $url );
}, 10, 4 );
Serve preview audio URL for free tier
add_filter( 'benecaster_feed_enclosure_url', function ( string $url, int $episode_id, int $show_id, string $tier_slug ): string {
if ( 'free' === $tier_slug ) {
$preview_url = get_post_meta( $episode_id, '_preview_audio_url', true );
if ( $preview_url ) {
return $preview_url;
}
}
return $url;
}, 10, 4 );
Notes
The returned URL is placed directly in enclosure url="...". An invalid or empty return value produces a malformed feed item that podcast apps may reject.
Need this built rather than just documented? See our services →