How I get Discord notifications for new ASUS ROG BIOS releases
Contents
Preface
Same problem as with AMD, different vendor.
I don’t like Armoury Crate, and I don’t like checking the ASUS support page every few weeks to see if there’s a new BIOS for my board.
Since the AMD chipset notifier was already running, turning it into a BIOS notifier was the next best thing I could think of lol
This post only covers what’s different.
What’s actually different
Comparing both repos, index.js, state.js and discord.js are almost identical.
The real difference is where the data comes from:
| AMD notifier | ROG notifier | |
|---|---|---|
| Data source | HTML page, parsed with regexes | JSON API, the one the ASUS page uses itself |
| Config per board | page URL | page URL plus three IDs from the API request |
| Discord message | version, release date, file size, changelog | plus SHA-256 and a direct download link |
So src/amd.js became src/asus.js, src/config.js got three new fields, and src/discord.js got two more fields in the embed.
Finding the API
The ASUS support page of a board, e.g. ROG STRIX X870E-E GAMING WIFI, doesn’t have the BIOS list in its HTML.
It loads it afterwards from an API. That’s the same request the worker sends.
To find it:
- Open the board’s
helpdesk_biospage - Open the dev tools (F12), switch to the Network tab and reload the page
- Filter for
GetPDBIOS
There’s exactly one request:
https://rog.asus.com/support/webapi/ProductV2/GetPDBIOS?website=global&model=rog-strix-x870e-e-gaming-wifi&pdid=0&m1id=28607&cpu=&LevelTagId=231962&systemCode=rog
Three of those parameters are specific to the board:
| Parameter | Where to find it |
|---|---|
model |
the slug in the page URL, between /motherboards/<series>/ and /helpdesk_bios/ |
m1id |
only in the API request |
LevelTagId |
only in the API request |
m1id and LevelTagId aren’t shown anywhere on the page and have nothing to do with the product name, so the network tab is the only place to get them.
If that happens, the check fails and a failure notification tells me about it.
The worker builds the same URL from the config:
function buildApiUrl(config) {
const params = new URLSearchParams({
website: "global",
model: config.asusModel,
pdid: "0",
m1id: config.asusM1Id,
cpu: "",
LevelTagId: config.asusLevelTagId,
systemCode: "rog"
});
return "https://rog.asus.com/support/webapi/ProductV2/GetPDBIOS?" + params.toString();
}
Why JSON beats scraping
On AMD’s side, the worker strips the page down to plain text and runs regexes over it.
That works… but every redesign of the page can break it.
Here the worker gets structured JSON and just reads fields.
There’s no markup that can change under it, only the API itself:
async function fetchJson(url) {
const response = await fetch(url, {
method: "GET",
headers: {
"user-agent": "Mozilla/5.0 rog-bios-notifier/1.0",
"accept": "application/json"
}
});
if (!response.ok) {
throw new Error("Failed to load ASUS BIOS API. HTTP " + response.status);
}
return await response.json();
}
Picking the right BIOS
This is the response for my board, shortened to the fields the worker actually uses and to the three newest entries:
{
"Result": {
"Obj": [
{
"Name": "BIOS",
"Files": [
{
"Version": "2503",
"IsRelease": "1",
"ReleaseDate": "2026/09/22",
"FileSize": "18.32 MB",
"sha256": "8714A81CE9F6701F93014EAEF96439BAAD926C0B60283F23EEC4319EF7C3C386",
"Description": "\"1.Update to AGESA ComboAM5 PI 1.3.0.1d.<br/>2.Enhanced memory performance, system stability, and compatibility with CXMT memory chips for improved low-latency operation.<br/><br/>Before running the USB BIOS Flashback tool, please rename the BIOS file (A5557.CAP) using BIOSRenamer.\"",
"DownloadUrl": {
"Global": "/pub/ASUS/mb/BIOS/ROG-STRIX-X870E-E-GAMING-WIFI-ASUS-2503.ZIP"
}
},
{ "Version": "2402", "IsRelease": "1", "ReleaseDate": "2026/07/15" },
{ "Version": "2401", "IsRelease": "0", "ReleaseDate": "2026/06/26" }
]
},
{ "Name": "Firmware" }
]
}
}
Obj holds one group per download category, here BIOS and Firmware, and the worker only cares about BIOS.
Its Files list contains every BIOS release for the board, newest first.
Every entry also has an IsRelease flag.
When I wrote the worker, I only assumed there might be entries with "0". Looking at the full list later, 7 of the 29 builds for my board have one, like 2401, which was followed by 2402 three weeks later.
ASUS doesn’t document what the flag means, but none of those builds should trigger a notification if one ever ends up at the top.
So the worker takes the first entry marked as a release, and only falls back to the very first entry if none is:
export function extractLatestBios(json) {
const groups = json && json.Result && json.Result.Obj;
if (!Array.isArray(groups)) return null;
const biosGroup = groups.find((group) => group.Name === "BIOS");
if (!biosGroup || !Array.isArray(biosGroup.Files) || !biosGroup.Files.length) {
return null;
}
return biosGroup.Files.find((file) => file.IsRelease === "1") || biosGroup.Files[0];
}
Cleaning up the data
Two fields need a bit of cleanup before they can go into a Discord message.
The changelog comes as HTML with <br> tags, and ASUS wraps the whole text in literal quote characters:
export function normalizeChangelog(rawDescription) {
if (!rawDescription) return null;
return rawDescription
.replace(/<br\s*\/?>/gi, "\n")
.replace(/<[^>]+>/g, "")
.replace(/ /gi, " ")
.replace(/&/gi, "&")
.trim()
.replace(/^"+|"+$/g, "")
.trim();
}
The download URL is sometimes absolute and sometimes a path on ASUS’ CDN:
export function normalizeDownloadUrl(url) {
if (!url) return null;
return url.startsWith("http") ? url : "https://dlcdnets.asus.com" + url;
}
The version itself needs nothing.
BIOS versions are plain numbers like 2503, and compareVersions from the first part handles a single segment just fine.
What the Discord message adds
The embed is the same as in the first part, with three changes:
const link = data.downloadUrl || config.pageUrl;
// ...
if (data.sha256) {
fields.push({ name: "SHA-256", value: "`" + data.sha256 + "`", inline: false });
}
- The title links straight to the BIOS file instead of the support page, and only falls back to the page if there’s no download URL.
- The SHA-256 checksum is in the message, so the download can be verified before flashing.
- The title says “New BIOS released!” and the embed color is ROG red,
0xcc000e.
Adding another board
A target needs the three IDs from the API request on top of what the AMD version needs:
const TARGETS = [
{
id: "x870e-e-wifi",
productName: "ROG STRIX X870E-E GAMING WIFI BIOS",
pageUrl: "https://rog.asus.com/motherboards/rog-strix/rog-strix-x870e-e-gaming-wifi/helpdesk_bios/",
asusModel: "rog-strix-x870e-e-gaming-wifi",
asusM1Id: "28607",
asusLevelTagId: "231962"
}
];
If one of the three is missing, the worker refuses to run and says which target is incomplete.
The worker replaces them with underscores, so
id: "x870e-e-wifi" needs a secret named DISCORD_WEBHOOK_URL_X870E_E_WIFI.
Everything else, KV namespace, connecting the repo and the secret itself, works exactly like in the first part.
Final result
The worker checks every configured board once an hour.
When ASUS releases a new BIOS, one Discord message shows up with the version, release date, changelog, file size, checksum and a direct download link.
No Armoury Crate, no checking the support page.
You can find the project here: rog-bios-notifier
Cheers.