Game events SDK · Protocol v1

Send game events to Hovermate

Any game or mod can report kills, boss kills, laps, goals or moments of its own. Hovermate turns them into timeline markers and automatic highlight clips, exactly like its built-in game cards.

Clients for C#, C++, Java, Python, JavaScript and Lua · One file each, no dependencies · MIT License

How it works

  1. Your mod reports a moment. One call, such as event("bossKill"). The client sends it in the background and never blocks the game.
  2. Hovermate marks it. Every event becomes a marker on the recording and session timeline, with your label.
  3. The player chooses highlights. Your game gets its own card in Clips. For every type the player turns on, the event also saves a highlight clip.

While its SDK is on, Hovermate publishes its address, a token and its inbox folder in connection.json:

connection.json
{
  "version": 1,
  "url": "http://local.hovermate.gg/hovermate/v1/events",
  "token": "<random, base64url, 32 bytes>",
  "inbox": "C:\\Users\\<user>\\AppData\\Local\\Hovermate\\Sdk\\Inbox"
}

Read url from this file and use it exactly as written; never hard-code or change its host, port or path.

Clients read %LOCALAPPDATA%\Hovermate\Sdk\connection.json, or the path in HOVERMATE_SDK_CONNECTION for tests and isolated setups. They read it again after a 401, because the token changes whenever the SDK is switched off and on. No file means Hovermate is not installed or its SDK is off; clients then stay silent.

Three ways to send events

Use the first one your mod framework allows.

HTTP

Your mod can open a local network connection.

POST the event to the URL with Authorization: Bearer <token>. Requests with an Origin header are refused, so web pages cannot send events.

C#, C++, Java, Python, JavaScript; GDScript can use HTTPRequest.

Files

Your mod can write files but has no network.

Append one JSON event per line to {inbox}\<game.id>.jsonl; no token is needed. A sandbox that writes only inside the mod's folder, such as Cyber Engine Tweaks, appends to hovermate-events.jsonl there instead, and the player adds that file in the game's card.

Every client falls back to the inbox; the Lua client writes the mod-folder file.

Log lines

Your scripts can only print to the game's own log.

Print [HOVERMATE] followed by one JSON event; anything before the marker is ignored. The Witcher 3 scriptslog.txt and the Skyrim and Fallout 4 Papyrus.0.log are read automatically while the game runs.

Witcher 3 scripts, Papyrus, or any game with a script log.

An HTTP request

POST /hovermate/v1/events HTTP/1.1
Host: local.hovermate.gg
Authorization: Bearer <token from connection.json>
Content-Type: application/json
Content-Length: 95

{"v":1,"game":{"id":"com.example.mygame","name":"My Game"},"type":"kill","label":"Double kill"}

Answer: 202 {"accepted":1}.

A log line

A line in the game log
[12:00:01] [Script] [HOVERMATE] {"v":1,"game":{"id":"com.example.mygame","name":"My Game"},"type":"bossKill","context":"Act 2"}

For another log, the player adds it under Clips › the game's card › Read events from a file, or your hello proposes it.

Quick start

Each example sends hello, kill, bossKill and a custom parry event. These are the runnable files from the SDK's examples folder.

C#

Runs in
netstandard2.0, .NET Framework 4.7.2+ and .NET, with C# 7.3: Unity, BepInEx, MelonLoader.
Set up
Add HovermateEvents.cs to your mod project.
csharp/Program.cs
// Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
// Run: dotnet run
using System;
using Hovermate.Sdk;

HovermateEvents.Log = Console.WriteLine;
HovermateEvents.Init("com.example.sdkdemo", "SDK Demo", "SdkDemo.exe");
HovermateEvents.Hello(new HovermateOffer[] { "kill", "bossKill", HovermateOffer.Custom("parry", "Parry") });
HovermateEvents.Event("kill", label: "Double kill", tags: new[] { "headshot" }, context: "Act 2");
HovermateEvents.Event("bossKill", label: "The Warden", context: "Act 2");
HovermateEvents.Custom("parry", "Parry", label: "Perfect parry");
Console.WriteLine(HovermateEvents.Flush(2000) ? "sent" : "timed out");
HovermateEvents.Shutdown();

Run it in examples/csharp (the example targets .NET 8 or later):

dotnet run

C++

Runs in
C++17 on Windows. Header-only; uses WinHTTP and links winhttp.lib.
Set up
Include hovermate_events.h. Call hovermate::shutdown() before your DLL is unloaded, never inside DllMain.
cpp/example.cpp
// Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
// Build (Developer Command Prompt): cl /std:c++17 /EHsc /utf-8 /I..\..\cpp example.cpp
#include <cstdio>
#include "hovermate_events.h"

int main() {
    hovermate::set_log([](const std::string& message) { std::printf("%s\n", message.c_str()); });
    hovermate::init("com.example.sdkdemo", "SDK Demo", "SdkDemo.exe");
    hovermate::hello({ "kill", "bossKill", hovermate::Offer::custom("parry", "Parry") });
    hovermate::event("kill", { "Double kill", { "headshot" }, "Act 2" });
    hovermate::event("bossKill", { "The Warden", {}, "Act 2" });
    hovermate::custom("parry", "Parry", { "Perfect parry", {}, {} });
    std::printf("%s\n", hovermate::flush(2000) ? "sent" : "timed out");
    hovermate::shutdown();
    return 0;
}

Run it in a Developer Command Prompt, in examples/cpp:

cl /std:c++17 /EHsc /utf-8 /I..\..\cpp example.cpp

Java

Runs in
Java 8 and later: ModTheSpire, Forge, Fabric.
Set up
Copy HovermateEvents.java into your mod and change its package (or relocate it when shading) so copies in other mods cannot collide.
java/Example.java
// Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
// Run (Java 8+): javac -d out ../../java/HovermateEvents.java Example.java && java -cp out Example
import gg.hovermate.sdk.HovermateEvents;
import gg.hovermate.sdk.HovermateEvents.Offer;
import java.util.Arrays;

public class Example {
    public static void main(String[] args) {
        HovermateEvents.log = System.out::println;
        HovermateEvents.init("com.example.sdkdemo", "SDK Demo", "SdkDemo.exe");
        HovermateEvents.hello(Offer.of("kill"), Offer.of("bossKill"), Offer.custom("parry", "Parry"));
        HovermateEvents.event("kill", "Double kill", Arrays.asList("headshot"), "Act 2");
        HovermateEvents.event("bossKill", "The Warden", null, "Act 2");
        HovermateEvents.custom("parry", "Parry", "Perfect parry", null, null);
        System.out.println(HovermateEvents.flush(2000) ? "sent" : "timed out");
        HovermateEvents.shutdown(1000);
    }
}

Run it in examples/java:

javac -d out ../../java/HovermateEvents.java Example.java && java -cp out Example

Python

Runs in
Python 3.7 and later, standard library only.
Set up
Put hovermate_events.py next to your script.
python/example.py
"""Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).

Run from any folder: python example.py
"""
import os
import sys

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'python'))
import hovermate_events as hovermate  # noqa: E402

hovermate.set_log(print)
hovermate.init('com.example.sdkdemo', 'SDK Demo', 'SdkDemo.exe')
hovermate.hello(['kill', 'bossKill', hovermate.custom_offer('parry', 'Parry')])
hovermate.event('kill', label='Double kill', tags=['headshot'], context='Act 2')
hovermate.event('bossKill', label='The Warden', context='Act 2')
hovermate.custom('parry', 'Parry', label='Perfect parry')
print('sent' if hovermate.flush(2.0) else 'timed out')
hovermate.shutdown()

Run it in examples/python:

python example.py

JavaScript

Runs in
Node.js 18 and later, as an ES module.
Set up
Import hovermate-events.mjs.
js/example.mjs
// Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
// Run with Node.js 18+: node example.mjs
import * as hovermate from '../../js/hovermate-events.mjs';

hovermate.setLog(console.log);
hovermate.init('com.example.sdkdemo', 'SDK Demo', 'SdkDemo.exe');
hovermate.hello(['kill', 'bossKill', hovermate.customOffer('parry', 'Parry')]);
hovermate.event('kill', { label: 'Double kill', tags: ['headshot'], context: 'Act 2' });
hovermate.event('bossKill', { label: 'The Warden', context: 'Act 2' });
hovermate.custom('parry', 'Parry', { label: 'Perfect parry' });
console.log((await hovermate.flush(2000)) ? 'sent' : 'timed out');
await hovermate.shutdown();

Run it in examples/js:

node example.mjs

Lua

Runs in
Lua 5.1+ and LuaJIT: Cyber Engine Tweaks and other sandboxes without network.
Set up
Put hovermate_events.lua next to your init.lua. Events go to hovermate-events.jsonl in your mod folder: tell players to add that file in Hovermate.
lua/example.lua
-- Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py --watch).
-- Run from the folder that stands in for your mod folder: lua path/to/example.lua
-- It writes hovermate-events.jsonl into the current folder.
--
-- In a Cyber Engine Tweaks mod, copy hovermate_events.lua next to init.lua and call it from your events:
--   local hovermate = require("hovermate_events")
--   registerForEvent("onInit", function()
--     hovermate.init("com.example.mymod", "My Mod", "Cyberpunk2077.exe", { log = print })
--     hovermate.hello({ "kill", "bossKill", hovermate.customOffer("parry", "Parry") })
--   end)
-- Then tell your users to add <game>\bin\x64\plugins\cyber_engine_tweaks\mods\<your mod>\hovermate-events.jsonl
-- in Hovermate: Clips > the game's card > Read events from a file.

local here = (arg and arg[0] or ''):match('^(.*[\\/])') or './'
package.path = here .. '../../lua/?.lua;' .. package.path
local hovermate = require('hovermate_events')

hovermate.init('com.example.sdkdemo', 'SDK Demo', 'SdkDemo.exe', { log = print, writer = print })
print('transport', hovermate.transport())
hovermate.hello({ 'kill', 'bossKill', hovermate.customOffer('parry', 'Parry') })
hovermate.event('kill', { label = 'Double kill', tags = { 'headshot' }, context = 'Act 2' })
hovermate.event('bossKill', { label = 'The Warden', context = 'Act 2' })
hovermate.custom('parry', 'Parry', { label = 'Perfect parry' })
print('log line for script-only mods:', hovermate.logLine('kill', { label = 'Double kill' }))
hovermate.shutdown()

Run it in the folder that stands in for your mod folder:

lua example.lua

What the clients do

CallWhat it does
init(gameId, gameName, exe?)Starts the client. gameId is [a-z0-9._-]{3,64} and stays the same across versions of your mod; exe is the game's executable file name.
hello(offers, log?, files?)Announces the types your game can send, and optionally a log or mod-folder files to read.
event(type, {label, tags, context})Sends a standard event type.
custom(customType, customLabel, {…})Sends a type of your own; customLabel names its highlight toggle.
flush() / shutdown()Waits for queued events; stops the background worker when the mod unloads.
  • Calls return at once and never throw into the game.
  • A background worker sends events with timeouts of 250 ms and retries a failed request once.
  • The queue holds 256 events and drops the oldest.
  • When HTTP is unreachable, events go to the inbox for a few seconds.
  • Without connection.json the client stays silent and checks again every 5 seconds.
  • Every event carries game.name. Text is cut to the limits below and invalid tags are dropped instead of failing.

The Lua client (hovermate_events.lua) writes each event at once. It prefers hovermate-events.jsonl in the mod's folder, then the inbox where absolute paths are allowed, then [HOVERMATE] lines through a writer function you pass, such as print. logLine(type, options) returns such a line for you to print.

The event object

{ "v": 1,
  "game": { "id": "com.example.mygame", "name": "My Game", "exe": "MyGame.exe" },
  "type": "kill",
  "label": "Double kill",
  "tags": ["headshot"],
  "context": "Act 2 · The City",
  "time": "2026-11-09T21:15:03.120Z" }
FieldRequiredRule
vyes1
game.idyes[a-z0-9._-]{3,64}: reverse-DNS or a slug, stable across versions of the mod
game.namefirst eventIn the first event or hello of a session; at most 64 characters
game.exenoA file name only. Ties events to the detected game folder and lets that game start the replay buffer
typeyesA standard type below, or custom
customType, customLabelfor custom[a-z0-9_.-]{1,32}, and a toggle name of at most 40 characters
labelnoText for this one event (marker tooltip, clip title), at most 80 characters
tagsnoAt most 8, each [a-z0-9_-]{1,24}
contextnoMap, level or area used in highlight titles, at most 64 characters
timenoISO-8601 UTC. Hovermate stamps the arrival time; a time within 5 s of it refines the stamp

Lengths count UTF-16 code units, so an emoji counts as 2. Text fields refuse control characters. Unknown fields are ignored, so later versions only add fields. Do not send personal data: labels and contexts can appear in clip titles and file names.

Standard types

29 types shared with Hovermate's built-in game cards. The list only grows; an unknown standard type is refused.

  • kill
  • death
  • assist
  • multikill
  • roundWon
  • roundLost
  • matchWon
  • matchLost
  • mvp
  • goal
  • save
  • objective
  • mission
  • lap
  • bestLap
  • position
  • crash
  • incident
  • penalty
  • pit
  • finish
  • raceWon
  • landing
  • takeoff
  • fightWon
  • levelUp
  • bossKill
  • zone
  • achievement

Custom types

For moments the standard list does not cover. Each custom type becomes its own highlight toggle, named by customLabel; a game can have up to 200.

{ "v": 1, "game": { "id": "com.example.mygame" },
  "type": "custom", "customType": "parry", "customLabel": "Parry", "label": "Perfect parry" }

hello

Optional and recommended. It creates your game's card with its highlight toggles before the first real event, so the player can set it up early.

{ "v": 1,
  "game": { "id": "com.example.mygame", "name": "My Game", "exe": "MyGame.exe" },
  "type": "hello",
  "offers": ["kill", "death", "bossKill", { "customType": "parry", "customLabel": "Parry" }],
  "log": "%LOCALAPPDATA%\\MyGame\\Saved\\Logs\\MyGame.log",
  "files": ["C:\\Games\\MyGame\\mods\\my_mod\\hovermate-events.jsonl"] }
  • offers lists standard types and { "customType", "customLabel" } objects.
  • log proposes a game log with [HOVERMATE] lines; files proposes mod-folder event files. Hovermate asks the player before it reads any proposed path.
  • hello works over every transport, including a [HOVERMATE] log line.

Limits and responses

LimitValue
Events per game20 per second. More gives 429 and nothing in that request is accepted; hello does not count
Batch{ "events": [ … ] } with 1 to 100 events, all or nothing. A new game must be named in the first event of the batch that mentions it
HTTP body64 KB
Inbox and file lines4 KB; longer lines are dropped
Custom types200 per game
Client timeouts250 ms or less, at most one retry, a bounded queue that drops the oldest event
StatusMeaning
202Accepted: {"accepted": n}
400Invalid: {"error", "detail", "index"}. index names the first invalid event of a batch
401Missing or wrong token: read connection.json again
403The request has an Origin header
413Body over 64 KB
429More than 20 events per second for one game
405, 411, 415Not a POST, no Content-Length, or not a JSON content type

Error codes: invalid_json, version, game, type, custom, label, tags, context, offers, log, files, batch. Lines that fail in files are counted and shown in the game's card as invalid events; Hovermate never logs them raw.

Test without Hovermate

The mock receiver mock-server.py (Python 3.8+, standard library) answers exactly like Hovermate. It writes its own connection.json into a temporary folder, applies the same rules and error codes, tails its inbox and the files you pass with --watch, and prints every event and every error.

Unlike Hovermate, the mock listens on a numeric loopback port and writes that address into its connection.json. It is only a test tool: clients use whatever url the file holds.

python tools/mock-server.py
# In another terminal, with the path it printed:
$env:HOVERMATE_SDK_CONNECTION = "C:\Users\you\AppData\Local\Temp\hovermate-sdk-mock-…\connection.json"
python examples/python/example.py
Mock output
Hovermate SDK mock listening on http://127.0.0.1:52777/hovermate/v1/events
[21:15:03.120] http  com.example.sdkdemo hello offers=kill,bossKill,custom:parry
[21:15:03.131] http  com.example.sdkdemo kill "Double kill" tags=headshot context="Act 2"
[21:15:03.140] http  com.example.sdkdemo bossKill "The Warden" context="Act 2"
[21:15:03.148] http  com.example.sdkdemo custom:parry "Parry" "Perfect parry"
  • --watch FILE tails a mod-folder hovermate-events.jsonl or a game log with [HOVERMATE] lines.
  • --follow-proposals reads what a hello proposes, as if the player allowed it.
  • --record FILE appends every accepted and refused event as a JSON line, for automated checks.

HOVERMATE_SDK_CONNECTION works the same with Hovermate itself, so tests never touch the real connection file.

Conformance cases

The SDK download includes conformance/cases.json: requests with the response Hovermate must give, from valid events to every error code. conformance/run.py sends them to the mock or to any receiver. Hovermate runs the same file in its own test suite.

python conformance/run.py
# or against a running receiver:
python conformance/run.py --connection path\to\connection.json

What the player sees

  • The first valid event or hello of a new game.id creates an SDK card in Clips, with your game's name, its folder and cover when exe matches an installed game, and a highlight toggle for every offered or seen type.
  • Highlight toggles start off: the player chooses which types save clips. Markers are always written.
  • Events follow the same path as built-in cards: journal, markers in clips and sessions, the highlight window and notifications.
  • When game.exe matches the game in the foreground, events count for that game. Otherwise they are accepted while a recording or the replay buffer is running.
  • Invalid events are counted in the card's status, so players can tell you something is wrong.

Protocol and files

The protocol v1 is the full contract: connection discovery, the four ways to deliver events, the event object, hello and Hovermate's behaviour.

The SDK (clients, examples, tools, conformance cases and protocol) is MIT licensed, so you can ship a client inside your mod. Hovermate itself is not covered by that license.