MMFP Govee Webhook listener and General Govee tool — turn GoSlimStratum (GSS) and GSS Miners (GSSM) notification webhooks into Govee light effects. Flash the ceiling fan light green/gold five times when a block is found, hold a lamp red while a miner is offline, switch it back when it recovers.
An MMFP Solutions product. Open source under GPLv3 (see LICENSE).
GSS / GSSM ── generic webhook (POST /hook, X-MMFP-Token) ──▶ MMFP Govee ──▶ Govee cloud API ──▶ lights
Two listeners:
| Listener | Port | Purpose |
|---|---|---|
| Web app | 3008 | Devices, event mappings, tokens, activity, settings |
| Webhook | 8787 | Receives GSS / GSSM generic-webhook events |
The webhook endpoint routes on the event type in the body (event_type from GSSM, type from
GSS), so one URL serves every event a channel sends. Effects run asynchronously — the webhook is
acknowledged immediately, effects are serialized per device, and a per-mapping cooldown collapses
event storms.
-
Run it (Docker):
docker run -d -p 3008:3008 -p 8787:8787 -v ./config:/app/config \ ghcr.io/mmfpsolutions/mmfp-govee:latest
Or locally:
go build ./cmd/mmfp-govee && ./mmfp-govee(config is created on first run). -
Add your Govee API key — open
http://localhost:3008, you'll land on Settings. Get a key in the Govee Home app (Profile → Settings → Apply for API Key). The key is stored encrypted at rest. -
Create a token on the Tokens page — the dialog shows the secret once, with copy-paste GSS/GSSM setup values.
-
Point GSS or GSSM at it — add a generic webhook channel:
- GSSM (its webhook form only takes a URL):
http://<this-machine>:8787/hook?token=<your secret> - Callers that support custom headers: URL
http://<this-machine>:8787/hook+ headerX-MMFP-Token: <your secret>(keeps the token out of URLs and logs). GSSM's backend supports aheadersmap too, but only via hand-editing notifications.json.
Use the sender's Test button — it returns 200 and shows up on the Activity page. Note the Test buttons emit fixed events (GSS:
startup, GSSM:test), not your mapped events — they prove the wiring, not the mapping. To preview a mapping's effect, use its own Test button in MMFP Govee, or force the event:curl "http://<host>:8787/hook/<event>?token=<secret>". - GSSM (its webhook form only takes a URL):
-
Create a mapping on the Mappings page: pick the token, the event(s) (e.g.
block_found), the device(s), and an effect (presets included: Celebrate, Alert, All clear, Off). Hit its Test button to see the lights fire.
POST /hook event type read from the JSON body (GSSM event_type / GSS type)
POST /hook/{event} forced-event mode (body optional)
GET /hook/{event} same, curl-friendly
Auth: X-MMFP-Token header or ?token= query param. Responses: 202 queued / no mapping,
200 test events, 400 no event type, 403 bad token.
config/config.json — see config.example.json. Managed from the UI;
ports require a restart. Secrets (goveeApiKey, token secrets) are encrypted at rest automatically
(ENC: prefix); plaintext values are migrated on first load.
Optional login: authentication is off by default. To enable it, set
"disableAuthentication": false and create in the config directory:
access.json—{"<username>": "<sha256-hex-of-password>"}jsonWebTokenKey.json—{"jsonWebTokenKey": "<random-secret>", "expiresIn": "1h"}
Quiet hours: suppress effects inside a time window (webhooks are still received and logged).
The window is evaluated against the app's own clock, which in Docker is the container's
timezone — not your laptop's. A container defaulting to UTC will evaluate a 22:00–07:00 window
several hours off and fire alerts in the early morning. Either set TZ on the container
(TZ=America/New_York, as docker-compose.yml does) or pin it in config so it cannot drift:
"quietHours": { "enabled": true, "start": "22:00", "end": "07:00", "timezone": "America/New_York" }The Settings page shows the app's current clock and whether it is suppressing right now — check there first if quiet hours misbehaves. Startup logs the effective window too.
Govee devices with LAN Control enabled in the Govee Home app can be driven directly over your network instead of the cloud API — roughly 10× faster, no API budget, and they keep working with the internet down. It's per-device and automatic: anything without it falls back to the cloud, so you can adopt it one lamp at a time. The LAN Control column on the Devices page shows a green check for devices currently served over LAN.
Scenes always use the cloud API (the LAN protocol has no scene command), so an effect that ends on a scene still costs one cloud call — versus a dozen for the flash itself.
Defaults are on; the block is optional:
"lan": { "enabled": true }Linux hosts need nothing else, but LAN discovery is UDP multicast, so the container must share
the host's network — network_mode: host (already set in docker-compose.yml). Leave devices
empty: discovery finds everything and proves each device is really answering.
Docker Desktop for Mac / Windows can't do multicast discovery (containers run in a VM, so host mode shares the VM's network, not yours) — but unicast to a known IP works fine. Give each device a DHCP reservation, list them, and use the Mac service:
"lan": {
"enabled": true,
"devices": [
{ "device": "34:FD:CC:44:A9:3A:60:AC", "sku": "H607C", "ip": "192.168.7.75" }
]
}docker compose up -d mmfp-govee-mac # bridge networking + publishes 4002/udpDevice IDs and models come from the Devices page. Only list devices with reserved IPs — a static entry pointing at a drifted address silently does nothing (the app notices and falls back to cloud, but you lose the speed-up without an obvious reason). Static entries are ignored wherever discovery finds the device anyway, so they're harmless to leave in a shared config.
./build-local.sh # compiles Tailwind CSS + builds the Docker image (no cache)
go test ./... # unit testsAny event type the sender emits can be mapped. Known types are offered in the mapping editor:
- GSS:
block_found,block_matured,block_orphaned,payment_*,node_*,miner_connect,miner_disconnect,best_share,notable_share,rejected_shares,network_difficulty_*,startup,shutdown,cleanup - GSSM:
miner_offline,miner_online,miner_failover,miner_zero_hashrate,miner_temp_high,miner_temp_normal,pool_offline,pool_online,node_offline,node_online,system_startup,system_shutdown,test