How to Re-Upload and Replace User-External Audio in Your Game Ahead of the Upcoming Privacy Update

Asset Delivery api:

Do not use this for licensed audio, doing so you’re breaking terms of use.

Instead use the toolbox service, example of the audio with id in your image: https://apis.roblox.com/toolbox-service/v1/items/details?assetIds=11717918968

Downloading licensed audio out of Roblox is against the Terms of Use, however if the audio isn’t licensed here is how to do it.

Send a post request to this endpoint: https://assetdelivery.roblox.com/v1/assets/batch
You have to include your cookie in it, so example headers for it would be e.g.:

"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "en-US,en;q=0.9",
"content-length": "49",
"content-type": "application/json",
"cookie": "YOUR COOKIE USE",
"origin": "https://music.roblox.com",
"priority": "u=1, i",
"referer": "https://music.roblox.com/",
"roblox-browser-asset-request": "true",
"roblox-place-id": "0",
"sec-ch-ua": '"Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
"x-csrf-token": ""

And json payload would be e.g.:

[
    {
        "requestId": "123456789",
        "assetId": 123456789
    }
]

Doing this will output an audio file/blob url in the json response from Roblox

Do not use this for licensed audio, doing so you’re breaking terms of use.

3 Likes

yes thats the method, which is against the tos, and the secuirty is pretty weak, to get the audio you jsut send some header params and thats it.

1 Like

i hypothetically did this and i got [{‘errors’: [{‘code’: 403, ‘message’: ‘User is not authorized to access Asset.’, ‘customErrorCode’: 1}], ‘requestId’: ‘9254155693’, ‘IsHashDynamic’: False, ‘IsCopyrightProtected’: False, ‘isArchived’: False, ‘assetTypeId’: 0}]

That’s odd, ill look into this endpoint more later and just update this reply. Maybe they enhanced this endpoint?

Update:

I’ve temporary made an endpoint which uses the asset delivery endpoint:
https://wokenews.net/roblox-audio

Simply do a get request by adding a query ?id=roblox_audio_id.

Example response is:

{
  "status": "success",
  "audio_url": "https://cdn.wokenews.net/output_audio/audio.ogg"
}

It’s built on PHP, code:

<?php

header("Content-Type: application/json");

if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["id"])) {
    $audio_id = htmlspecialchars($_GET["id"]);
    $cookie = "YOUR ROBLOX COOKIE";
    $output_dir = "OUTPUT DIRECTORY";
    $api_url = "https://assetdelivery.roblox.com/v1/assets/batch";

    function generate_random_filename() {
        return bin2hex(random_bytes(16));
    }

    function download_roblox_audio($audio_id, $cookie, $output_dir, $api_url) {
        $payload = json_encode([
            ["requestId" => $audio_id, "assetId" => $audio_id]
        ]);

        $headers = [
            "accept: */*",
            "accept-encoding: gzip, deflate, br, zstd",
            "accept-language: en-US,en;q=0.9",
            "content-length: " . strlen($payload),
            "content-type: application/json",
            "cookie: $cookie",
            "origin: https://music.roblox.com",
            "priority: u=1, i",
            "referer: https://music.roblox.com/",
            "roblox-browser-asset-request: true",
            "roblox-place-id: 0",
            'sec-ch-ua: "Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"',
            "sec-ch-ua-mobile: ?0",
            'sec-ch-ua-platform: "Windows"',
            "sec-fetch-dest: empty",
            "sec-fetch-mode: cors",
            "sec-fetch-site: same-site",
            "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
            "x-csrf-token: "
        ];

        $ch = curl_init($api_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        $response = curl_exec($ch);
        $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($http_status !== 200) {
            return ["error" => "Failed to fetch audio", "status" => $http_status];
        }

        $data = json_decode($response, true);
        if (isset($data[0]["location"])) {
            $audio_url = $data[0]["location"];
            $random_filename = generate_random_filename() . ".ogg";
            $audio_path = $output_dir . $random_filename;
            
            if (!file_put_contents($audio_path, fopen($audio_url, 'r'))) {
                return ["error" => "Failed to download audio"];
            }
            return ["audio_path" => $audio_path, "random_filename" => $random_filename];
        }
        return ["error" => "Invalid response"];
    }

    try {
        $download = download_roblox_audio($audio_id, $cookie, $output_dir, $api_url);
        if (isset($download["error"])) {
            echo json_encode($download);
            exit;
        }

        $cdn_url = str_replace($output_dir, "https://YOUR_DOMAIN/output_audio/", $download["audio_path"]);
        echo json_encode(["status" => "success", "audio_url" => $cdn_url]);
    } catch (Exception $e) {
        echo json_encode(["error" => $e->getMessage()]);
    }
} else {
    echo json_encode(["error" => "Invalid request"]);
}
?>

For cookie part as I see it requires: GuestData=UserID=-, .RBXIDCHECK=, RBXImageCache=timg=, RBXSessionTracker=sessionid=, _t=, rbx-ip2=rbx-ip2, rbxas=, RBXEventTrackerV2=, UnifiedLoggerSession=, .ROBLOSECURITY=