How I wrote a Cloudflare Worker that notifies about new AMD Chipset Drivers using Discord Webhooks
Preface
There is currently no good way to get notified when AMD releases new chipset drivers.
If you care about keeping systems up to date, you usually end up doing one of two things.
- Manually checking the AMD driver pages from time to time.
- Joining the AMD Discord server and trying to follow release announcements.
Neither option is great.
Manually checking the website is tedious and easy to forget. The AMD Discord server is large and noisy.
There is no dedicated channel that only announces chipset driver updates. Messages about GPU drivers, marketing announcements and community events are kinda mixed together.
Forwarding that into your own Discord server using the “Channel Following”-Feature quickly becomes useless.
Instead of a clean update feed you end up with a stream of irrelevant notifications.
I wanted a simple solution: a service that checks AMD’s driver pages periodically and sends exactly one notification when a new chipset driver appears.
That became this project: GitHub: amd-chipset-notifier
The entire system runs as a Cloudflare Worker.
Architecture
The worker performs a very small set of tasks:
- Fetch chipset driver metadata from AMD
- Compare it with the last known version
- Send a Discord webhook if the version changed
- Persist the new state in Cloudflare KV
This makes the system completely serverless. There is no VM, no container and no cron job running somewhere.
Cloudflare executes the worker on a schedule using a Cron Trigger.
The persistent state is stored in Workers KV so the worker remembers the last detected version across executions.
The stored structure currently looks like this:
{
"productName": "AMD Chipset Drivers",
"version": "8.02.18.557",
"releaseDate": "2026-03-09",
"pageUrl": "https://www.amd.com/en/support/downloads/drivers.html/chipsets/am5/x870e.html",
"checkedAt": "2026-03-15T12:00:44.154Z"
}
The worker only cares about the version field for change detection.
Internally this state is stored under a single KV key. Each worker run loads the state, performs a comparison and either updates it or only refreshes the timestamp.
Observability endpoints
During development I added two HTTP endpoints which expose the internal state and execution logic.
These endpoints make debugging significantly easier when running the worker in production.
The endpoints are implemented directly inside the worker entrypoint.
In src/index.js:
if (url.pathname === "/run") {
const result = await runCheck(env, { forceNotify: false, manual: true });
return jsonResponse(result, 200);
}
Because the endpoint calls the same internal logic used by the scheduled worker run, the behaviour is identical to a real execution.
/state
Returns the current stored driver state.
Example:
{
"ok": true,
"state": {
"productName": "AMD Chipset Drivers",
"version": "8.02.18.557",
"releaseDate": "2026-03-09",
"pageUrl": "https://www.amd.com/en/support/downloads/drivers.html/chipsets/am5/x870e.html",
"checkedAt": "2026-03-16T12:00:44.154Z"
}
}
This endpoint reads directly from KV and returns whatever the worker considers the latest known version.
In src/index.js:
if (url.pathname === "/state") {
const config = getConfig(env);
validateConfig(config);
const state = await loadStoredState(env, config.kvKey);
return jsonResponse(
{
ok: true,
state: state || null
},
200
);
}
/run
Forces a manual check and returns a detailed execution report.
Example response:
{
"ok": true,
"initialized": false,
"changed": false,
"notified": false,
"manual": true,
"previous": {
"productName": "AMD Chipset Drivers",
"version": "8.02.18.557",
"releaseDate": "2026-03-09",
"pageUrl": "https://www.amd.com/en/support/downloads/drivers.html/chipsets/am5/x870e.html",
"checkedAt": "2026-03-16T12:00:44.154Z"
},
"current": {
"productName": "AMD Chipset Drivers",
"version": "8.02.18.557",
"releaseDate": "2026-03-09",
"pageUrl": "https://www.amd.com/en/support/downloads/drivers.html/chipsets/am5/x870e.html",
"checkedAt": "2026-03-16T12:16:01.452Z"
}
}
The flags in this response describe exactly what happened during execution.
| Field | Meaning |
|---|---|
| initialized | KV was empty and got initialized |
| changed | the detected version differs from the stored version |
| notified | a Discord webhook was sent |
| manual | execution was triggered via API |
This endpoint effectively acts as a debugging interface for the worker.
/notify-test
Runs a check and always sends a Discord notification, because /notify-run sets data.forceNotify to true.
In src/index.js:
if (url.pathname === "/notify-test") {
// Sends a Discord notification even if the version has not changed
const result = await runCheck(env, { forceNotify: true, manual: true });
return jsonResponse(result, 200);
}
And inside src/discord.js:
if (data.forceNotify) {
return {
content:
"\u200B\n" +
"**TEST NOTIFICATION**\n" +
"Product: " + config.productName + "\n" +
"Current version: " + data.currentVersion + "\n" +
"Release date: " + (data.releaseDate || "unknown") + "\n" +
"Page: " + config.amdPageUrl
};
}
Fetching the AMD driver version
AMD does not provide a public API for chipset driver releases. The worker therefore extracts metadata directly from the driver page.
The parsing logic pulls out three pieces of information:
- version
- release date
- download page
Excerpt from src/amd.js:
export async function fetchDriverInfo(config) {
const html = await fetchHtml(config.amdPageUrl);
const parsed = extractDriverInfo(html, config.productName);
if (!parsed.version) {
throw new Error("Could not find a version number on the AMD page");
}
return {
productName: config.productName,
version: parsed.version,
releaseDate: parsed.releaseDate,
pageUrl: config.amdPageUrl
};
}
The HTML is first downloaded with a custom User-Agent because some AMD endpoints reject requests without one.
async function fetchHtml(url) {
const response = await fetch(url, {
method: "GET",
headers: {
"user-agent": "Mozilla/5.0 amd-chipset-notifier/1.0",
"accept-language": "en-US,en;q=0.9"
}
});
if (!response.ok) {
throw new Error("Failed to load AMD page. HTTP " + response.status);
}
return await response.text();
}
The HTML is converted to plain text and parsed using regular expressions anchored to the product name.
In src/amd.js:
const versionRegex = new RegExp(
escapedName +
"[\\s\\S]{0,1200}?Revision Number[\\s\\S]{0,200}?([0-9]+(?:\\.[0-9]+)+)",
"i"
);
State management with Workers KV
Cloudflare KV is used as the persistence layer.
From src/state.js:
export async function loadStoredState(env, kvKey) {
return await env.STATE.get(kvKey, "json");
}
Writing a full state snapshot:
export async function saveCurrentState(env, kvKey, state) {
await env.STATE.put(kvKey, JSON.stringify(state));
}
If the version has not changed, only the timestamp is updated:
export async function updateCheckedAtOnly(env, kvKey, previousState) {
const updated = {
...previousState,
checkedAt: new Date().toISOString()
};
await env.STATE.put(kvKey, JSON.stringify(updated));
}
Workers KV is eventually consistent, but this does not matter for a single scheduled worker execution.
Version comparison
The worker compares semantic version numbers using a helper function.
In src/utils.js:
const versionDiff = compareVersions(current.version, previous.version);
const changed = versionDiff > 0;
The helper splits version segments and compares them numerically.
Sending the Discord webhook
Discord notifications are sent to all configured webhook URLs.
In src/discord.js:
export async function sendDiscordNotification(config, data) {
const payload = buildDiscordMessage(config, data);
for (const url of config.discordWebhookUrls) {
const response = await fetch(url, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload)
});
if (!response.ok) {
const body = await response.text();
throw new Error("Discord webhook error. HTTP " + response.status + " Body: " + body);
}
}
}
The message body is constructed separately.
function buildDiscordMessage(config, data) {
return {
content:
"\u200B\n" +
"**New AMD Chipset Driver released!**\n" +
"Product: " + config.productName + "\n" +
"Previous: " + data.previousVersion + "\n" +
"New: " + data.currentVersion + "\n" +
"Release date: " + (data.releaseDate || "unknown") + "\n" +
"Download: " + config.amdPageUrl
};
}
Worker execution flow
The central orchestration logic lives in runCheck, which coordinates fetching the current version, loading the previous state and deciding whether a notification should be sent.
In src/index.js:
const current = await fetchDriverInfo(config);
const previous = await loadStoredState(env, config.kvKey);
const versionDiff = compareVersions(current.version, previous.version);
const changed = versionDiff > 0;
If a new version appears the worker sends a Discord notification and updates the stored state.
Why Cloudflare Workers are perfect for this
This kind of job fits the Workers model extremely well.
The workload is tiny:
- one HTTP request to AMD
- one KV read
- optional KV write
- optional Discord webhook
Execution time is basically nonexistent. Running this on a traditional server would be unnecessary overhead.
Workers allow the entire system to exist as a few hundred lines of code.
Final result
The worker now runs periodically and checks AMD’s driver page. If the version changes, a single message appears in my Discord channel.
No AMD-Discord spam. No manual checks. Just a clean feed of chipset driver releases.
Exactly what I wanted.
Cheers.
You can find the Project here: amd-chipset-notifier