I specifically wrote “darksidesync” for this purpose. At the time to create a binding to LibUPnP. It does the exact same thing, it starts background threads for listening and all callbacks are on those background threads.
The main premise is that the main-application loop is on the Lua side, while the C lib runs the background threads and generates the call-backs
It’s been a while since I visited that code though.
Code is here: https://github.com/Tieske/DarkSideSync