


A dedicated-server mod that posts the stats only the server can see to an HTTP endpoint β by default the self-hosted gs dashboard, but it'll POST to any URL.
Pairs with the GsValheimStatsClient companion (which each player installs for their personal stats). Both POST to the same endpoint; a receiver merges them by world + player name.
π Full payload schema, key conventions, and merge rules are documented on the Data format & self-hosting wiki.
Why a server mod at all? Valheim hands creature simulation to whichever client is nearest, so most combat is captured client-side. The server still owns idle / distant creatures (e.g. a sleeping mob you sneak-attack), so this mod captures those β notably the true post-backstab hardest hit β plus world-progression milestones the server is authoritative for.
onlinePlayers / worldDay), force-emitted on join/leave for near-instant "online now"Install on your dedicated server (depends on denikson-BepInExPack_Valheim). Drop the DLL in BepInEx/plugins/.
Launch once to generate BepInEx/config/net.cproudlock.gsvalheimstats.cfg, then set:
[General]
World = vhserver3 # your server's -world name
EmitIntervalSeconds = 120
[Ingest]
Url = http://localhost:3001/api/valheim/ingest # dashboard reachable from the server
Token = <bearer token>
Restart the server. Cumulative counters persist to BepInEx/config/<guid>.state.tsv.
Same contract as the client mod β an authenticated POST of a JSON snapshot (see that mod's README for a minimal receiver + curl test).
Payload (source: "server"):
{
"schemaVersion": 1,
"game": "valheim",
"source": "server",
"world": "vhserver3",
"hostName": "Proudlock VH Server 3",
"serverStartedAtUtc": "2026-06-09T11:32:00Z",
"onlinePlayers": ["Erebus", "Pridetoes"],
"worldDay": 12,
"emittedAtUtc": "2026-06-09T18:00:00Z",
"snapshotIdLocal": "9a6bβ¦",
"players": [{
"name": "Erebus",
"boss": [{ "boss": "Eikthyr", "kills": 1, "damageDealt": 1450, "fightSec": 95 }],
"weapons": [{ "weapon": "Knives", "damageDealt": 80, "kills": 2, "hardestHit": 79 }]
}],
"bossKillEvents": [{ "boss": "Eikthyr", "fightSec": 95, "firstBlood": "Erebus", "topDamagePlayer": "Erebus", "topDamage": 1450, "participants": 2, "tsUtc": "β¦" }],
"milestones": [{ "key": "defeated_eikthyr", "label": "Eikthyr defeated", "kind": "boss", "tsUtc": "β¦" }],
"mods": [{ "guid": "Azumatt.AzuCraftyBoxes", "name": "AzuCraftyBoxes", "version": "1.8.14", "author": "Azumatt", "folder": "Azumatt-AzuCraftyBoxes-1.8.14" }]
}
Each feed reports the creatures it simulates, so they're disjoint. A receiver should:
damageDealt / kills per (player, weapon) across the two sources (exact total, no double-count, since a single hit is only ever processed by one side), andhardestHit.The gs dashboard does this with per-source rows + SUM / MAX(β¦) at read time.