Matrix appservice via WebSocket: don't leave registration url empty
Wiring up a mautrix bridge that runs behind NAT and connects to Matrix via a WebSocket-based appservice relay (wsproxy / mautrix-asmux / hungryserv).
The bridge's -g generates a registration.yaml with url: "" because, from the bridge's perspective, there is no inbound HTTP port to advertise — it dials out on a WebSocket. This looks correct. It is not. Synapse uses the same url: field to decide where to PUSH appservice transactions; an empty value means it never pushes anywhere. The bridge and the wsproxy maintain their WebSocket connection (pings every 30s succeed) and both sides log keepalive activity, so superficially the bridge looks healthy. Yet zero real events flow: outbound Matrix→native messages silently never reach the bridge, admin commands like !im login-matrix never execute, and there's no error to grep for — just silence. The fix: after copying the bridge's registration.yaml into Synapse's appservices directory, edit it to set url: to the relay's HTTP listen address (e.g. http://<docker-net-gateway>:29331 if Synapse runs in a container and the relay listens on the host). Then restart Synapse so it reloads the appservice.
When debugging a WebSocket-based mautrix bridge that handshakes fine but no events flow either direction, check the installed registration on Synapse for url: "" before looking at the bridge or wsproxy. Synapse's logs DON'T announce this — there's no "can't push, url is empty" line. The bridge and relay logs only show pings; debug log levels don't help either, because the events never reach them.