Symptom
Inline <video> and <audio> elements pointing at https://arweave.net/{tx_id} fail to play on iPhone and iPad (all iOS/iPadOS versions, Safari and every other iOS browser, which all use WebKit). Desktop Chrome/Firefox/Safari and Android Chrome play the same URLs without issue.
Production impact
Any site embedding Arweave-hosted media via the canonical arweave.net/{id} URL is broken for iOS traffic network-wide. Concrete example: ArchiTangle Book+ has printed physical books carrying QR codes that resolve to arweave.net/{tx_id} video clips. Those books are already in readers' hands and cannot be reprinted.
Evidence
$ curl -sIL -H 'Range: bytes=0-1' \
'https://arweave.net/17MIDCT7HcYSX2C_ybksF8AbI05IBqu4C94oFz_hwEc'
HTTP/2 200 # should be 206
# no Accept-Ranges, no Content-Range, full 99,844,286-byte body streamed
$ curl -sIL -H 'Range: bytes=0-1' \
'https://arweave.net/raw/17MIDCT7HcYSX2C_ybksF8AbI05IBqu4C94oFz_hwEc'
HTTP/2 206
accept-ranges: bytes
content-range: bytes 0-1/99844286
iOS network trace shows the request carries X-Playback-Session-Id (WebKit's media-player probe). The response is 200 without Accept-Ranges, and the console logs Failed to load resource: Plug-in handled load — WebKit's signal that it refused to hand the resource to the media pipeline.
Root cause
dev_arweave:get_tx/3 serves GET /{tx_id} and returns the data body with status 200 and no range headers. dev_arweave:get_raw/3 serves GET /raw/{tx_id} and correctly emits Accept-Ranges: bytes plus 206 Partial Content on range requests.
Codec signature proof
The two paths are signed by different codecs. The tx@1.0 response codec used by get_tx/3 signs a field list that does not include accept-ranges, content-length, or content-range — so even if the headers were added downstream, they would invalidate the signature and be stripped. The /raw codec signs over exactly those fields, which is why only /raw can legitimately return 206 today. A fix must route through the /raw codec, not patch headers onto the tx codec.
Why iOS requires 206
Apple mandates byte-range support for media elements on iOS: "your server must support byte-range requests". WebKit enforces this in WebKitWebSourceGStreamer.cpp L1089-L1098: a 200 response to a range request is treated as "server does not support seeking," and the media pipeline refuses the resource. Desktop browsers tolerate the same response; iOS does not. RFC 7233 §4.1 specifies 206 as the correct response.
Proposed fix
Route the data-embedded branch of get_tx/3 through the already-working get_raw/3 pipeline:
get_tx(Base, Request, Opts) ->
case find_key(<<"tx">>, Base, Request, Opts) of
not_found -> {error, not_found};
TXID ->
ExcludeData = hb_util:bool(
find_key(<<"exclude-data">>, Base, Request, Opts)),
case ExcludeData of
true ->
request(<<"GET">>, <<"/tx/", TXID/binary>>,
Opts#{ exclude_data => true });
false ->
%% Delegate to the range-aware /raw pipeline so
%% Accept-Ranges/206/Content-Range are emitted.
get_raw(#{ <<"raw">> => TXID }, Request, Opts)
end
end.
Happy to open a PR.
Regression window
arweave.net/{id} served 206 correctly in early April 2026 and began returning 200 mid-April, coinciding with Forward Research's migration of arweave.net to HyperBEAM (context: ar-io/ar-io-node#619). /raw/{id} is unaffected.
Environment
HyperBEAM edge @ 3c6a617869967d5356a59ce342f12c933a58691b (2026-04-21). arweave.net production (Server: CDN77-Turbo fronting HyperBEAM). iOS 18.7 Safari + iOS 18.7 Chrome (both WebKit) — broken. macOS Safari + Chrome and Android 14 Chrome — work.
Symptom
Inline
<video>and<audio>elements pointing athttps://arweave.net/{tx_id}fail to play on iPhone and iPad (all iOS/iPadOS versions, Safari and every other iOS browser, which all use WebKit). Desktop Chrome/Firefox/Safari and Android Chrome play the same URLs without issue.Production impact
Any site embedding Arweave-hosted media via the canonical
arweave.net/{id}URL is broken for iOS traffic network-wide. Concrete example: ArchiTangle Book+ has printed physical books carrying QR codes that resolve toarweave.net/{tx_id}video clips. Those books are already in readers' hands and cannot be reprinted.Evidence
iOS network trace shows the request carries
X-Playback-Session-Id(WebKit's media-player probe). The response is 200 withoutAccept-Ranges, and the console logsFailed to load resource: Plug-in handled load— WebKit's signal that it refused to hand the resource to the media pipeline.Root cause
dev_arweave:get_tx/3servesGET /{tx_id}and returns the data body with status 200 and no range headers.dev_arweave:get_raw/3servesGET /raw/{tx_id}and correctly emitsAccept-Ranges: bytesplus206 Partial Contenton range requests.Codec signature proof
The two paths are signed by different codecs. The
tx@1.0response codec used byget_tx/3signs a field list that does not includeaccept-ranges,content-length, orcontent-range— so even if the headers were added downstream, they would invalidate the signature and be stripped. The/rawcodec signs over exactly those fields, which is why only/rawcan legitimately return 206 today. A fix must route through the/rawcodec, not patch headers onto the tx codec.Why iOS requires 206
Apple mandates byte-range support for media elements on iOS: "your server must support byte-range requests". WebKit enforces this in
WebKitWebSourceGStreamer.cppL1089-L1098: a 200 response to a range request is treated as "server does not support seeking," and the media pipeline refuses the resource. Desktop browsers tolerate the same response; iOS does not. RFC 7233 §4.1 specifies 206 as the correct response.Proposed fix
Route the data-embedded branch of
get_tx/3through the already-workingget_raw/3pipeline:Happy to open a PR.
Regression window
arweave.net/{id}served 206 correctly in early April 2026 and began returning 200 mid-April, coinciding with Forward Research's migration of arweave.net to HyperBEAM (context: ar-io/ar-io-node#619)./raw/{id}is unaffected.Environment
HyperBEAM
edge@3c6a617869967d5356a59ce342f12c933a58691b(2026-04-21). arweave.net production (Server: CDN77-Turbo fronting HyperBEAM). iOS 18.7 Safari + iOS 18.7 Chrome (both WebKit) — broken. macOS Safari + Chrome and Android 14 Chrome — work.