For events you need event loop, which detects that the actual event (say, data from network) happens, and then generates the the software event structure and calls appropriate event handler, or in more complex systems, chain of event handlers, until a handler marks the event accepted. If there's no handler, or no registered handler accepts the event, event is ignored.
Often event loop is in a library, which has API for application to have event handlers, and send application specific events, in addition to any events the library itself may produce. One problem with event based applications is, it is often complicated to use two libraries which both want to have their own event loop, unless library developer took extra care to allow using other event loop than library's own.
Unless it is a very low level real time system, it's critical that event loop does not do busy wait. In Linux/Unix/Posix code, event loop typically works around select() or poll() system function. When there are no events, event loop calls this function with timeout matching time of next timer event (if there are timer events). In addition to timeout, select()/poll() will also return if any of specified file handles (often network or IPC sockets) are ready for reading/writing/error, as well as if there is an interrupt which is not otherwise handled. Then event loop code checks why the function returned, generates and dispatches necessary events, and when all is done, calls the select()/poll() function again.
Also important in event based systems is, event handler must not block, because it is function called by event loop, so event loop does not continue somewhere in the background, handler function call is part of event loop. So handler function must process only available data, preferably quickly, then store necessary state to continue later, and return to wait for next event. For operations that must block, another thread must be launched. Also for long computations, the computation must either be chopped to small pieces to allow event loop to run too, or computation must happen in another thread. The annoying "Not responding" in GUI application title bar typically means just this: application programmer was lazy/incompetent and blocked event loop, so it can't react to OS events.
So, yes, it is quite easy to have event based system with C. Just have a loop with select()/poll() in it, define event types, create data structs for events, and have a list of function pointers to be called with a new event struct as parameter, for each event type.