diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,24 @@
 # Revision history for eventlog-socket
 
+## 0.1.3.0 -- 2026-03-27
+
+- Add support for hooks that fire at certain points in the `eventlog-socket` lifecycle.
+
+  This adds `registerHook` and `eventlog_socket_register_hook` to the Haskell and C APIs, respectively, as well as two kinds of hooks:
+  - A _post-startEventLogging_ hook, which is triggered whenever event logging is started.
+  - A _pre-endEventLogging_ hook, which is triggered whenever event logging is ended.
+
+  These hooks are intended to give package writers the ability to add their own init events, which are resent event time that a new client connects. For instance, the following code registers a post-startEventLogging hook that greets every new client with a user message event:
+
+  ```haskell
+  registerHook HookPostStartEventLogging $
+    traceEventIO "Hello, new user!"
+  ```
+
+  To avoid races, it is important that these hooks are registered *before* `eventlog-socket` is started.
+
+- Add support for builtin `startProfiling` and `stopProfiling` control commands.
+
 ## 0.1.2.0 -- 2026-03-25
 
 **Warning**: The C API exposed from this version contains breaking changes over the C API exposed from version 0.1.1.0. This is justified by the fact that the C API exposed from version 0.1.1.0 is broken and that version is deprecated and not known to be in use.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@
 
 The `eventlog-socket` package installs a C header file, `eventlog_socket.h`, which enables you to instrument your application from a C main function. There is one reason you may want to instrument your application from C, which is that you want to capture *all* of the GHC eventlog. If your application is instrumented from Haskell, it loses the first few events. See the note [above](#default-writer).
 
-For an example of an application instrumented from a custom C main, see [`examples/fibber-c-main`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.2.0/examples/fibber-c-main/).
+For an example of an application instrumented from a custom C main, see [`examples/fibber-c-main`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.3.0/examples/fibber-c-main/).
 
 ### Configure your application to enable the eventlog
 
@@ -81,19 +81,70 @@
   ./my-app +RTS -l -RTS
   ```
 
+## Lifecycle Hooks
+
+Every time a new client connects, the GHC RTS resends the eventlog header and initialisation events.
+If you develop a package that communicates over the eventlog, you may find that your package *also* needs to resent certain initialisation events.
+The `eventlog-socket` package provides lifecycle hooks for this purpose.
+There are two supported hooks:
+
+1. The *post-startEventLogging* hook triggers *after* each time event logging is started.
+2. The *pre-endEventLogging* hook triggers *before* each time event logging is ended.
+
+To register a handler for lifecycle hook, use the `registerHook` function.
+For instance, the following snippet registers a hook that sends a user message event every time event logging is started.
+
+```haskell
+module Main where
+
+import Data.Foldable (for_)
+import Debug.Trace (traceEventIO)
+import GHC.Eventlog.Socket (Hook (..), registerHook, start)
+import System.Environment (lookupEnv)
+
+main :: IO ()
+main = do
+  -- Register the greeting hook:
+  registerHook HookPostStartEventLogging $
+    traceEventIO "Hello, new user!"
+
+  -- Start eventlog-socket on GHC_EVENTLOG_UNIX_PATH, if set:
+  maybeUnixPath <- lookupEnv "GHC_EVENTLOG_UNIX_PATH"
+  for_ maybeUnixPath $ \unixPath -> do
+    putStrLn "Start eventlog-socket on " <> unixPath
+    start unixPath
+
+  -- The rest of your application:
+  ...
+```
+
+> ⚠️
+> To avoid races, it is important that lifecycle hooks are registered *before* `eventlog-socket` is started.
+
+> ⚠️
+> The post-startEventLogging and pre-endEventLogging lifecycle hooks usually correspond to clients connecting and disconnecting, respectively.
+> The only exception is if your application is instrumented from a C main, in which case the *first* post-startEventLogging hook triggers immediately after the GHC RTS is initialised.
+> In this scenario, the first client *does not* trigger the post-startEventLogging hook, but subsequent clients *do*.
+
+For an example of an application that registers lifecycle hooks using the Haskell API, see [`examples/fibber`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.3.0/examples/fibber/).
+
+For an example of an application that registers lifecycle hooks using the C API, see [`examples/fibber-c-main`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.3.0/examples/fibber-c-main/).
+
 ## Control Commands
 
 When compiled with the `+control` feature flag, the eventlog socket supports *control commands*. These are messages that can be *written to* the eventlog socket by the client to control the RTS or execute custom control commands.
 
-The `eventlog-socket` package provides three built-in commands:
+The `eventlog-socket` package provides five built-in commands:
 
-1. The `startHeapProfiling` control command starts heap profiling.
-2. The `stopHeapProfiling` control command stops heap profiling.
-3. The `requestHeapCensus` control command requests a single heap profile.
+1. The `startProfiling` control command starts cost-centre profiling.
+2. The `stopProfiling` control command stops cost-centre profiling.
+3. The `startHeapProfiling` control command starts heap profiling.
+4. The `stopHeapProfiling` control command stops heap profiling.
+5. The `requestHeapCensus` control command requests a single heap profile.
 
 The heap profiling commands require that the RTS is initialised with one of the `-h` options. See [RTS options for heap profiling](https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html#rts-options-heap-prof). If any heap profiling option is passed, heap profiling is started by default. To avoid this, pass the `--no-automatic-heap-samples` to the RTS *in addition to* the selected `-h` option, e.g., `./my-app +RTS -hT --no-automatic-heap-samples -RTS`.
 
-The [`eventlog-socket-control`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.2.0/eventlog-socket-control/) package provides utilities for creating control command protocol messages to write to the eventlog socket.
+The [`eventlog-socket-control`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.3.0/eventlog-socket-control/) package provides utilities for creating control command protocol messages to write to the eventlog socket.
 If you want to send control commands from a different programming language, see [Control Command Protocol](#control-command-protocol) for the specification of the binary control command protocol.
 
 ### Control Command Protocol
@@ -111,11 +162,13 @@
 command-id       = byte;   (* commands are assigned numeric IDs *)
 ```
 
-The built-in commands live in the `"eventlog-socket"` namespace and are numbered in order starting at 3.
+The built-in commands live in the `"eventlog-socket"` namespace and are numbered in order starting at 1.
 
-1. The message for `startHeapProfiling` is `\xF0\x9E\x97\x8C\x00\x15eventlog-socket\x03`.
-2. The message for `stopHeapProfiling` is `\xF0\x9E\x97\x8C\x00\x15eventlog-socket\x04`.
-3. The message for `requestHeapCensus` is `\xF0\x9E\x97\x8C\x00\x15eventlog-socket\x05`.
+1. The message for `startProfiling` is `\xF0\x9E\x97\x8C\x00\x15eventlog-socket\x01`.
+2. The message for `stopProfiling` is `\xF0\x9E\x97\x8C\x00\x15eventlog-socket\x02`.
+3. The message for `startHeapProfiling` is `\xF0\x9E\x97\x8C\x00\x15eventlog-socket\x03`.
+4. The message for `stopHeapProfiling` is `\xF0\x9E\x97\x8C\x00\x15eventlog-socket\x04`.
+5. The message for `requestHeapCensus` is `\xF0\x9E\x97\x8C\x00\x15eventlog-socket\x05`.
 
 For example, a simple Python client using the [`socket`](https://docs.python.org/3/library/socket.html) library could request a heap census as follows:
 
@@ -135,120 +188,42 @@
 1.  Register a namespace. This must be a string of between 1 and 255 bytes. By convention, this should be your Haskell package name.
 2.  Register each command. This must be a number between 1 and 255. By convention, this should be the first non-zero number that's still available for this namespace.
 
-Commands can be passed some user data at registration time. This is intended to let you defines multiple different commands using the same handler.
-
-For an example of an application instrumented with custom control commands using the Haskell API, see [`examples/custom-command`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.2.0/examples/custom-command/).
-
-For an example of an application instrumented with custom control commands using the C API, see [`examples/custom-command-c-main`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.2.0/examples/custom-command-c-main/).
-
-The following snippet defines the `registerGreeters` function, which registers two commands via the Haskell API:
+To register a namespace, use the `registerNamespace` function.
+To register a custom command handler, use the `registerCommand` function.
+For instance, the following snippet registers two custom commands under the `"greeters"` namespace which greet C and Haskell, respectively.
 
 ```haskell
-import GHC.Eventlog.Socket
+module Main where
 
+import Data.Foldable (for_)
+import Debug.Trace (traceEventIO)
+import GHC.Eventlog.Socket (Hook (..), registerHook, start)
+import System.Environment (lookupEnv)
+
 greeter :: String -> IO ()
 greeter name =
   putStrLn $ "Hello, " <> name <> "!"
 
-registerGreeters :: IO ()
-registerGreeters = do
+main :: IO ()
+main = do
+  -- Register the custom command handlers:
   myNamespace <- registerNamespace "greeters"
   registerCommand myNamespace (CommandId 1) (greeter "C")
   registerCommand myNamespace (CommandId 2) (greeter "Haskell")
-```
 
-The following snippet defines the `register_greeters` function, which registers two equivalent commands via the C API:
-
-```c
-#include <stdio.h>
-#include <stdlib.h>
-#include <eventlog_socket.h>
-
-static void greeter(const EventlogSocketControlNamespace *const namespace,
-                       const EventlogSocketControlCommandId command_id,
-                       const void *user_data) {
-  (void) namespace;
-  (void) command_id;
-  printf("Hello, %s!", (const char*)user_data);
-}
-
-bool register_greeters(void) {
-  // Use the name of my Haskell package:
-  const char* const my_namespace = "my-app";
-
-  // Allocate space for the namespace object:
-  EventlogSocketControlNamespace *namespace = NULL;
-
-  // Try to register the namespace:
-  const EventlogSocketStatus namespace_status =
-    eventlog_socket_control_register_namespace(
-      strlen(my_namespace), my_namespace, &namespace);
-
-  // Handle any errors:
-  if (namespace_status.ess_status_code != EVENTLOG_SOCKET_OK) {
-    char *errmsg = eventlog_socket_strerror(namespace_status);
-    if (errmsg != NULL) {
-      fprintf(stderr, "ERROR: %s", errmsg);
-      free(errmsg);
-    }
-    return false;
-  }
-  // Otherwise, namespace contains a valid object.
-
-  // Use the first available non-zero command ID for greet_c:
-  const uint8_t greet_c_id = 1;
-
-  // Allocate data for the greet_c command, if needed:
-  static const char * const greet_c_data = "C";
-
-  // Try to register the greet_c command:
-  const EventlogSocketStatus greet_c_status =
-    eventlog_socket_control_register_command(
-      namespace, greet_c_id, greeter, greet_c_data);
-
-  // Handle any errors:
-  if (greet_c_status.ess_status_code != EVENTLOG_SOCKET_OK) {
-    char *errmsg = eventlog_socket_strerror(greet_c_status);
-    if (errmsg != NULL) {
-      fprintf(stderr, "ERROR: %s", errmsg);
-      free(errmsg);
-    }
-    return false;
-  }
-  // Otherwise, the command is registered.
-
-  // Use the next available command ID for greet_haskell:
-  const uint8_t greet_haskell_id = 2;
-
-  // Allocate data for the greet_haskell command, if needed:
-  static const char * const greet_haskell_data = "Haskell";
-
-  // Try to register the greet_haskell command:
-  const EventlogSocketStatus greet_haskell_status =
-    eventlog_socket_control_register_command(
-      namespace, greet_haskell_id, greeter, greet_haskell_data);
-
-  // Handle any errors:
-  if (greet_haskell_status.ess_status_code != EVENTLOG_SOCKET_OK) {
-    char *errmsg = eventlog_socket_strerror(greet_haskell_status);
-    if (errmsg != NULL) {
-      fprintf(stderr, "ERROR: %s", errmsg);
-      free(errmsg);
-    }
-    return false;
-  }
-  // Otherwise, the command is registered.
+  -- Start eventlog-socket on GHC_EVENTLOG_UNIX_PATH, if set:
+  maybeUnixPath <- lookupEnv "GHC_EVENTLOG_UNIX_PATH"
+  for_ maybeUnixPath $ \unixPath -> do
+    putStrLn "Start eventlog-socket on " <> unixPath
+    start unixPath
 
-  return true;
-}
+  -- The rest of your application:
+  ...
 ```
 
-## Contributing
-
-The `scripts` directory contains various scripts for contributors to this repository.
-
-The `./scripts/test.sh` script runs the test suite, which is located in [`eventlog-socket-tests`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.2.0/eventlog-socket-tests/).
+> ⚠️
+> To avoid races, it is important that custom commands are registered *before* `eventlog-socket` is started.
 
-The `./scripts/pre-commit.sh` script runs the pre-commit hooks, which contains various formatters and linters.
+For an example of an application instrumented with custom control commands using the Haskell API, see [`examples/custom-command`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.3.0/examples/custom-command/).
 
-The `./scripts/build-doxygen.sh` and `./scripts/build-haddock.sh` scripts build the documentation for the C and Haskell APIs.
+For an example of an application instrumented with custom control commands using the C API, see [`examples/custom-command-c-main`](https://github.com/well-typed/eventlog-socket/tree/eventlog-socket-0.1.3.0/examples/custom-command-c-main/).
diff --git a/cbits/eventlog_socket.c b/cbits/eventlog_socket.c
--- a/cbits/eventlog_socket.c
+++ b/cbits/eventlog_socket.c
@@ -22,6 +22,7 @@
 
 #include "./eventlog_socket/debug.h"
 #include "./eventlog_socket/error.h"
+#include "./eventlog_socket/hooks.h"
 #include "./eventlog_socket/init_state.h"
 #include "./eventlog_socket/string.h"
 #include "./eventlog_socket/worker.h"
@@ -340,6 +341,14 @@
   RETURN_ON_ERROR(STATUS_FROM_PTHREAD(pthread_mutex_lock(&g_mutex)));
   // If the RTS is not marked as ready...
   if (!(g_init_state & EVENTLOG_SOCKET_SIG_RTS_READY)) {
+    // If EventLogSocketWriter is already attached...
+    if (g_init_state & EVENTLOG_SOCKET_SIG_ATTACHED) {
+      // ...trigger the post-startEventLogging hooks.
+      DEBUG_DEBUG("%s", "Trigger post-startEventLogging hooks.");
+      RETURN_ON_ERROR_CLEANUP(
+          es_hooks_handle_hook(EVENTLOG_SOCKET_HOOK_POST_START_EVENT_LOGGING),
+          pthread_mutex_unlock(&g_mutex));
+    }
     // ...broadcast on the relevant condition and...
     RETURN_ON_ERROR_CLEANUP(
         STATUS_FROM_PTHREAD(pthread_cond_broadcast(&g_ghc_rts_ready_cond)),
@@ -627,4 +636,12 @@
 #else
   return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);
 #endif /* EVENTLOG_SOCKET_FEATURE_CONTROL */
+}
+
+/* PUBLIC - see documentation in eventlog_socket.h */
+EventlogSocketStatus
+eventlog_socket_register_hook(EventlogSocketHook hook,
+                              EventlogSocketHookHandler *hook_handler,
+                              const void *hook_data) {
+  return es_hooks_register_hook(hook, hook_handler, hook_data);
 }
diff --git a/cbits/eventlog_socket/control.c b/cbits/eventlog_socket/control.c
--- a/cbits/eventlog_socket/control.c
+++ b/cbits/eventlog_socket/control.c
@@ -7,7 +7,7 @@
 ///            and `eventlog_socket_control_register_command`.
 /// @author    Wen Kokke
 /// @author    Matthew Pickering
-/// @version   0.1.2.0
+/// @version   0.1.3.0
 /// @date      2025-2026
 /// @copyright BSD-3-Clause License.
 ///
@@ -37,6 +37,7 @@
 #include "./poll.h"
 #include "eventlog_socket.h"
 #include "init_state.h"
+#include "rts/prof/CCS.h"
 
 // CONTROL_MAGIC should be the UTF-8 encoding of some code point between
 // U+010000 and U+10FFFF. Let's pick code point U+01E5CC, for Eventlog
@@ -77,11 +78,11 @@
 /// @brief The name of the builtin namespace.
 #define BUILTIN_NAMESPACE "eventlog-socket"
 
-/// @brief The ID reserved for the builtin `StartEventLogging` command.
-#define BUILTIN_COMMAND_ID_START_EVENT_LOGGING 1
+/// @brief The ID of the builtin `StartProfiling` command.
+#define BUILTIN_COMMAND_ID_START_PROFILING 1
 
-/// @brief The ID reserved for the builtin `EndEventLogging` command.
-#define BUILTIN_COMMAND_ID_END_EVENT_LOGGING 2
+/// @brief The ID of the builtin `StopProfiling` command.
+#define BUILTIN_COMMAND_ID_STOP_PROFILING 2
 
 /// @brief The ID of the builtin `StartHeapProfiling` command.
 #define BUILTIN_COMMAND_ID_START_HEAP_PROFILING 3
@@ -96,7 +97,30 @@
  * Handlers for Builtin Commands
  ******************************************************************************/
 
-/// @brief Handler for "StartHeapProfiling" command (eventlog-socket::0).
+/// @brief Handler for "StartProfiling" command (eventlog-socket::1).
+static void es_control_start_profiling(
+    const EventlogSocketControlNamespace *const namespace,
+    const EventlogSocketControlCommandId command_id, const void *user_data) {
+  (void)namespace;
+  (void)command_id;
+  (void)user_data;
+  startProfTimer();
+  DEBUG_DEBUG("%s", "Started profiling.");
+}
+
+/// @brief Handler for "StopProfiling" command (eventlog-socket::2).
+static void
+es_control_stop_profiling(const EventlogSocketControlNamespace *const namespace,
+                          const EventlogSocketControlCommandId command_id,
+                          const void *user_data) {
+  (void)namespace;
+  (void)command_id;
+  (void)user_data;
+  stopProfTimer();
+  DEBUG_DEBUG("%s", "Stopped profiling.");
+}
+
+/// @brief Handler for "StartHeapProfiling" command (eventlog-socket::3).
 static void es_control_start_heap_profiling(
     const EventlogSocketControlNamespace *const namespace,
     const EventlogSocketControlCommandId command_id, const void *user_data) {
@@ -107,7 +131,7 @@
   DEBUG_DEBUG("%s", "Started heap profiling.");
 }
 
-/// @brief Handler for "StopHeapProfiling" command (eventlog-socket::1).
+/// @brief Handler for "StopHeapProfiling" command (eventlog-socket::4).
 static void es_control_stop_heap_profiling(
     const EventlogSocketControlNamespace *const namespace,
     const EventlogSocketControlCommandId command_id, const void *user_data) {
@@ -118,7 +142,7 @@
   DEBUG_DEBUG("%s", "Stopped heap profiling.");
 }
 
-/// @brief Handler for "RequestHeapCensus" command (eventlog-socket::2).
+/// @brief Handler for "RequestHeapCensus" command (eventlog-socket::5).
 static void es_control_request_heap_census(
     const EventlogSocketControlNamespace *const namespace,
     const EventlogSocketControlCommandId command_id, const void *user_data) {
@@ -193,21 +217,37 @@
     .next = NULL,
     .command_registry =
         &(EventlogSocketControlCommand){
-            .command_id = BUILTIN_COMMAND_ID_START_HEAP_PROFILING,
-            .command_handler = es_control_start_heap_profiling,
+            .command_id = BUILTIN_COMMAND_ID_START_PROFILING,
+            .command_handler = es_control_start_profiling,
             .command_data = NULL,
             .next =
                 &(EventlogSocketControlCommand){
-                    .command_id = BUILTIN_COMMAND_ID_STOP_HEAP_PROFILING,
-                    .command_handler = es_control_stop_heap_profiling,
+                    .command_id = BUILTIN_COMMAND_ID_STOP_PROFILING,
+                    .command_handler = es_control_stop_profiling,
                     .command_data = NULL,
                     .next =
                         &(EventlogSocketControlCommand){
                             .command_id =
-                                BUILTIN_COMMAND_ID_REQUEST_HEAP_CENSUS,
-                            .command_handler = es_control_request_heap_census,
+                                BUILTIN_COMMAND_ID_START_HEAP_PROFILING,
+                            .command_handler = es_control_start_heap_profiling,
                             .command_data = NULL,
-                            .next = NULL,
+                            .next =
+                                &(EventlogSocketControlCommand){
+                                    .command_id =
+                                        BUILTIN_COMMAND_ID_STOP_HEAP_PROFILING,
+                                    .command_handler =
+                                        es_control_stop_heap_profiling,
+                                    .command_data = NULL,
+                                    .next =
+                                        &(EventlogSocketControlCommand){
+                                            .command_id =
+                                                BUILTIN_COMMAND_ID_REQUEST_HEAP_CENSUS,
+                                            .command_handler =
+                                                es_control_request_heap_census,
+                                            .command_data = NULL,
+                                            .next = NULL,
+                                        },
+                                },
                         },
                 },
         },
diff --git a/cbits/eventlog_socket/hooks.c b/cbits/eventlog_socket/hooks.c
new file mode 100644
--- /dev/null
+++ b/cbits/eventlog_socket/hooks.c
@@ -0,0 +1,149 @@
+#include <string.h>
+
+#include "./error.h"
+#include "./hooks.h"
+#include "eventlog_socket.h"
+
+/******************************************************************************
+ * Hook Handler Registry
+ ******************************************************************************/
+
+/// @brief An entry in the hook registry.
+typedef struct EventlogSocketHookHandlerEntry EventlogSocketHookHandlerEntry;
+
+/// @brief An entry in the handler registry for one particular hook.
+struct EventlogSocketHookHandlerEntry {
+  /// @brief The user-provided hook handler.
+  EventlogSocketHookHandler *const hook_handler;
+  /// @brief The user-provided data for the hook handler.
+  const void *hook_data;
+  /// @brief The pointer to the next entry in the hook registry.
+  EventlogSocketHookHandlerEntry *next;
+};
+
+/// @brief The registry for all hooks.
+typedef struct {
+  EventlogSocketHookHandlerEntry *on_connect_handlers;
+  EventlogSocketHookHandlerEntry *on_disconnect_handlers;
+} EventlogSocketHookHandlerRegistry;
+
+/// @brief The global hook handler registry.
+static EventlogSocketHookHandlerRegistry g_hook_handler_registry = {
+    .on_connect_handlers = NULL,
+    .on_disconnect_handlers = NULL,
+};
+
+/// @brief The mutex that guards the global hook handler registry.
+static pthread_mutex_t g_hook_handler_registry_mutex =
+    PTHREAD_MUTEX_INITIALIZER;
+
+/* HIDDEN - see documentation in hooks.h */
+HIDDEN EventlogSocketStatus es_hooks_register_hook(
+    const EventlogSocketHook hook,
+    EventlogSocketHookHandler *const hook_handler, const void *hook_data) {
+  // Validate the arguments.
+  if (hook_handler == NULL) {
+    errno = EINVAL;
+    return STATUS_FROM_ERRNO();
+  }
+
+  // Acquire the `g_hook_handler_registry_mutex`.
+  RETURN_ON_ERROR(
+      STATUS_FROM_PTHREAD(pthread_mutex_lock(&g_hook_handler_registry_mutex)));
+
+  // Get a pointer to the *first* entry for the given hook.
+  EventlogSocketHookHandlerEntry **entry = NULL;
+  switch (hook) {
+  case EVENTLOG_SOCKET_HOOK_POST_START_EVENT_LOGGING:
+    entry = &g_hook_handler_registry.on_connect_handlers;
+    break;
+  case EVENTLOG_SOCKET_HOOK_PRE_END_EVENT_LOGGING:
+    entry = &g_hook_handler_registry.on_disconnect_handlers;
+    break;
+  default:
+    errno = EINVAL;
+    return STATUS_FROM_ERRNO();
+  }
+  if (entry == NULL) {
+    errno = EINVAL;
+    return STATUS_FROM_ERRNO();
+  }
+
+  // Get a pointer to the *last* entry for the given hook.
+  assert(entry != NULL);
+  while (*entry != NULL) {
+    // TODO: Check if the hook is already registered?
+    entry = &(*entry)->next;
+    assert(entry != NULL);
+  }
+
+  // Allocate memory and write the new hook handler and data.
+  assert(*entry == NULL);
+  *entry = malloc(sizeof(EventlogSocketHookHandlerEntry));
+  if (*entry == NULL) {
+    RETURN_ON_ERROR_CLEANUP(
+        STATUS_FROM_ERRNO(), // `malloc` sets errno.
+        pthread_mutex_unlock(&g_hook_handler_registry_mutex));
+  }
+  assert(*entry != NULL);
+  const EventlogSocketHookHandlerEntry hook_handler_entry =
+      (EventlogSocketHookHandlerEntry){
+          .hook_handler = hook_handler,
+          .hook_data = hook_data,
+          .next = NULL,
+      };
+  memcpy(*entry, &hook_handler_entry, sizeof(EventlogSocketHookHandlerEntry));
+
+  // Release the `g_hook_handler_registry_mutex`.
+  RETURN_ON_ERROR(STATUS_FROM_PTHREAD(
+      pthread_mutex_unlock(&g_hook_handler_registry_mutex)));
+
+  // Return OK.
+  return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);
+}
+
+/* HIDDEN - see documentation in hooks.h */
+HIDDEN EventlogSocketStatus
+es_hooks_handle_hook(const EventlogSocketHook hook) {
+
+  // Acquire the `g_hook_handler_registry_mutex`.
+  RETURN_ON_ERROR(
+      STATUS_FROM_PTHREAD(pthread_mutex_lock(&g_hook_handler_registry_mutex)));
+
+  // Get a pointer to the *first* entry for the given hook.
+  EventlogSocketHookHandlerEntry **entry = NULL;
+  switch (hook) {
+  case EVENTLOG_SOCKET_HOOK_POST_START_EVENT_LOGGING:
+    entry = &g_hook_handler_registry.on_connect_handlers;
+    break;
+  case EVENTLOG_SOCKET_HOOK_PRE_END_EVENT_LOGGING:
+    entry = &g_hook_handler_registry.on_disconnect_handlers;
+    break;
+  default:
+    errno = EINVAL;
+    return STATUS_FROM_ERRNO();
+  }
+  if (entry == NULL) {
+    errno = EINVAL;
+    return STATUS_FROM_ERRNO();
+  }
+
+  // Loop through and call each hook handler.
+  assert(entry != NULL);
+  while (*entry != NULL) {
+    EventlogSocketHookHandler *const hook_handler = (*entry)->hook_handler;
+    if (hook_handler != NULL) {
+      const void *hook_data = (*entry)->hook_data;
+      hook_handler(hook_data);
+    }
+    entry = &(*entry)->next;
+    assert(entry != NULL);
+  }
+
+  // Release the `g_hook_handler_registry_mutex`.
+  RETURN_ON_ERROR(STATUS_FROM_PTHREAD(
+      pthread_mutex_unlock(&g_hook_handler_registry_mutex)));
+
+  // Return OK.
+  return STATUS_FROM_CODE(EVENTLOG_SOCKET_OK);
+}
diff --git a/cbits/eventlog_socket/hooks.h b/cbits/eventlog_socket/hooks.h
new file mode 100644
--- /dev/null
+++ b/cbits/eventlog_socket/hooks.h
@@ -0,0 +1,17 @@
+#ifndef EVENTLOG_SOCKET_HOOKS_H
+#define EVENTLOG_SOCKET_HOOKS_H
+
+#include "./macros.h"
+#include "eventlog_socket.h"
+
+/// @brief Register a new hook handler.
+///
+/// @see eventlog_socket_register_hook
+HIDDEN EventlogSocketStatus es_hooks_register_hook(
+    EventlogSocketHook hook, EventlogSocketHookHandler *hook_handler,
+    const void *hook_data);
+
+/// @brief Call all handlers for a given `EventlogSocketHook`.
+HIDDEN EventlogSocketStatus es_hooks_handle_hook(EventlogSocketHook hook);
+
+#endif /* EVENTLOG_SOCKET_HOOKS_H */
diff --git a/cbits/eventlog_socket/hsapi.c b/cbits/eventlog_socket/hsapi.c
new file mode 100644
--- /dev/null
+++ b/cbits/eventlog_socket/hsapi.c
@@ -0,0 +1,84 @@
+/// @file      hsapi.c
+/// @brief     Helper functions for the @c GHC.Eventlog.Socket module.
+/// @details   This module defines wrappers for some of the functions
+///            exported by @c eventlog_socket.h for use in the Haskell
+///            module @c GHC.Eventlog.Socket.
+/// @author    Wen Kokke
+/// @version   0.1.3.0
+/// @date      2025-2026
+/// @copyright BSD-3-Clause License.
+///
+
+#include "./hsapi.h"
+#include <string.h>
+
+HIDDEN void es_hsapi_start(EventlogSocketStatus *eventlog_socket_status,
+                           EventlogSocketAddr *eventlog_socket_addr,
+                           EventlogSocketOpts *eventlog_socket_opts) {
+  const EventlogSocketStatus status =
+      eventlog_socket_start(eventlog_socket_addr, eventlog_socket_opts);
+  memcpy(eventlog_socket_status, &status, sizeof(EventlogSocketStatus));
+}
+
+HIDDEN void es_hsapi_from_env(EventlogSocketStatus *eventlog_socket_status,
+                              EventlogSocketAddr *eventlog_socket_addr,
+                              EventlogSocketOpts *eventlog_socket_opts) {
+  const EventlogSocketStatus status =
+      eventlog_socket_from_env(eventlog_socket_addr, eventlog_socket_opts);
+  memcpy(eventlog_socket_status, &status, sizeof(EventlogSocketStatus));
+}
+
+HIDDEN char *es_hsapi_strerror(EventlogSocketStatus *eventlog_socket_status) {
+  return eventlog_socket_strerror(*eventlog_socket_status);
+}
+
+HIDDEN void
+es_hsapi_register_hook(EventlogSocketStatus *eventlog_socket_status,
+                       EventlogSocketHook eventlog_socket_hook,
+                       EventlogSocketHookHandler eventlog_socket_hook_handler,
+                       const void *eventlog_socket_hook_data) {
+  const EventlogSocketStatus status = eventlog_socket_register_hook(
+      eventlog_socket_hook, eventlog_socket_hook_handler,
+      eventlog_socket_hook_data);
+  memcpy(eventlog_socket_status, &status, sizeof(EventlogSocketStatus));
+}
+
+HIDDEN void
+es_hsapi_worker_status(EventlogSocketStatus *eventlog_socket_status_out) {
+  const EventlogSocketStatus eventlog_socket_status =
+      eventlog_socket_worker_status();
+  memcpy(eventlog_socket_status_out, &eventlog_socket_status,
+         sizeof(EventlogSocketStatus));
+}
+
+HIDDEN void
+es_hsapi_control_status(EventlogSocketStatus *eventlog_socket_status_out) {
+  const EventlogSocketStatus eventlog_socket_status =
+      eventlog_socket_control_status();
+  memcpy(eventlog_socket_status_out, &eventlog_socket_status,
+         sizeof(EventlogSocketStatus));
+}
+
+HIDDEN void es_hsapi_control_register_namespace(
+    EventlogSocketStatus *eventlog_socket_status,
+    uint8_t eventlog_socket_namespace_len,
+    char eventlog_socket_namespace[eventlog_socket_namespace_len],
+    EventlogSocketControlNamespace **eventlog_socket_namespace_out) {
+  const EventlogSocketStatus status =
+      eventlog_socket_control_register_namespace(eventlog_socket_namespace_len,
+                                                 eventlog_socket_namespace,
+                                                 eventlog_socket_namespace_out);
+  memcpy(eventlog_socket_status, &status, sizeof(EventlogSocketStatus));
+}
+
+HIDDEN void es_hsapi_control_register_command(
+    EventlogSocketStatus *eventlog_socket_status,
+    EventlogSocketControlNamespace *eventlog_socket_namespace,
+    EventlogSocketControlCommandId eventlog_socket_command_id,
+    EventlogSocketControlCommandHandler eventlog_socket_command_handler,
+    const void *eventlog_socket_command_data) {
+  const EventlogSocketStatus status = eventlog_socket_control_register_command(
+      eventlog_socket_namespace, eventlog_socket_command_id,
+      eventlog_socket_command_handler, eventlog_socket_command_data);
+  memcpy(eventlog_socket_status, &status, sizeof(EventlogSocketStatus));
+}
diff --git a/cbits/eventlog_socket/hsapi.h b/cbits/eventlog_socket/hsapi.h
new file mode 100644
--- /dev/null
+++ b/cbits/eventlog_socket/hsapi.h
@@ -0,0 +1,48 @@
+/// @file      hsapi.h
+/// @brief     Helper functions for the @c GHC.Eventlog.Socket module.
+/// @details   This module declares wrappers for some of the functions
+///            exported by @c eventlog_socket.h for use in the Haskell
+///            module @c GHC.Eventlog.Socket.
+/// @author    Wen Kokke
+/// @version   0.1.3.0
+/// @date      2025-2026
+/// @copyright BSD-3-Clause License.
+///
+
+#include "./macros.h"
+#include <eventlog_socket.h>
+
+HIDDEN void es_hsapi_start(EventlogSocketStatus *eventlog_socket_status,
+                           EventlogSocketAddr *eventlog_socket_addr,
+                           EventlogSocketOpts *eventlog_socket_opts);
+
+HIDDEN void es_hsapi_from_env(EventlogSocketStatus *eventlog_socket_status,
+                              EventlogSocketAddr *eventlog_socket_addr,
+                              EventlogSocketOpts *eventlog_socket_opts);
+
+HIDDEN char *es_hsapi_strerror(EventlogSocketStatus *eventlog_socket_status);
+
+HIDDEN void
+es_hsapi_register_hook(EventlogSocketStatus *eventlog_socket_status,
+                       EventlogSocketHook eventlog_socket_hook,
+                       EventlogSocketHookHandler eventlog_socket_hook_handler,
+                       const void *eventlog_socket_hook_data);
+
+HIDDEN void
+es_hsapi_worker_status(EventlogSocketStatus *eventlog_socket_status_out);
+
+HIDDEN void
+es_hsapi_control_status(EventlogSocketStatus *eventlog_socket_status_out);
+
+HIDDEN void es_hsapi_control_register_namespace(
+    EventlogSocketStatus *eventlog_socket_status,
+    uint8_t eventlog_socket_namespace_len,
+    char eventlog_socket_namespace[eventlog_socket_namespace_len],
+    EventlogSocketControlNamespace **eventlog_socket_namespace_out);
+
+HIDDEN void es_hsapi_control_register_command(
+    EventlogSocketStatus *eventlog_socket_status,
+    EventlogSocketControlNamespace *eventlog_socket_namespace,
+    EventlogSocketControlCommandId eventlog_socket_command_id,
+    EventlogSocketControlCommandHandler eventlog_socket_command_handler,
+    const void *eventlog_socket_command_data);
diff --git a/cbits/eventlog_socket/worker.c b/cbits/eventlog_socket/worker.c
--- a/cbits/eventlog_socket/worker.c
+++ b/cbits/eventlog_socket/worker.c
@@ -16,12 +16,17 @@
 
 #include "./debug.h"
 #include "./error.h"
+#include "./hooks.h"
 #include "./init_state.h"
 #include "./poll.h"
 #include "./string.h"
 #include "./worker.h"
 #include "eventlog_socket.h"
 
+/******************************************************************************
+ * Worker Thread
+ ******************************************************************************/
+
 /// @brief The maximum length of the queue for pending connections.
 #define LISTEN_BACKLOG 5
 
@@ -291,7 +296,7 @@
   const bool should_stop =
       // ...some eventlog writer is currently running and either:
       is_running && (
-                        // ...(1) it's not EventlogSocketWriter, or...
+                        // ...(1) it's not EventLogSocketWriter, or...
                         !is_attached ||
                         // ...(2) we've already had our first connection.
                         //
@@ -309,6 +314,13 @@
 
   // We stop event logging.
   if (should_stop) {
+    // If the current eventlog writer *is* EventLogSocketWriter...
+    if (is_attached) {
+      // ...trigger the pre-endEventLogging hooks.
+      DEBUG_DEBUG("%s", "Trigger pre-endEventLogging hooks.");
+      EXIT_ON_ERROR(
+          es_hooks_handle_hook(EVENTLOG_SOCKET_HOOK_PRE_END_EVENT_LOGGING));
+    }
     DEBUG_DEBUG("%s", "Stopping current event logger.");
     endEventLogging();
   }
@@ -325,9 +337,18 @@
   // We start event logging with EventlogSocketWriter.
   if (should_start) {
     DEBUG_DEBUG("%s", "Starting new event logger.");
-    // TODO: Add retry loop.
-    const bool is_started = startEventLogging(&EventLogSocketWriter);
+    bool is_started = false;
+    while (!is_started) {
+      // TODO: Limit number of retries. Since this loop should not be blocking,
+      //       this requires exiting this listen step and remembering that
+      //       connection was left in an unfinished state, and retrying
+      //       startEventLogging the next time a listen step is entered.
+      is_started = startEventLogging(&EventLogSocketWriter);
+    }
     DEBUG_TRACE("is_started: %s", is_started ? "true" : "false");
+    DEBUG_DEBUG("%s", "Trigger post-startEventLogging hooks.");
+    EXIT_ON_ERROR(
+        es_hooks_handle_hook(EVENTLOG_SOCKET_HOOK_POST_START_EVENT_LOGGING));
   }
 
   // Update *g_worker_state.init_state_ptr to record that we've seen our first
diff --git a/eventlog-socket.cabal b/eventlog-socket.cabal
--- a/eventlog-socket.cabal
+++ b/eventlog-socket.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               eventlog-socket
-version:            0.1.2.0
+version:            0.1.3.0
 synopsis:           Stream GHC eventlog events to external processes.
 description:
   The @eventlog-socket@ package lets you stream the GHC eventlog over a socket in realtime.
@@ -29,6 +29,10 @@
   cbits/eventlog_socket/debug.h
   cbits/eventlog_socket/error.c
   cbits/eventlog_socket/error.h
+  cbits/eventlog_socket/hooks.c
+  cbits/eventlog_socket/hooks.h
+  cbits/eventlog_socket/hsapi.c
+  cbits/eventlog_socket/hsapi.h
   cbits/eventlog_socket/init_state.h
   cbits/eventlog_socket/macros.h
   cbits/eventlog_socket/poll.h
@@ -53,7 +57,7 @@
 source-repository this
   type:     git
   location: https://github.com/well-typed/eventlog-socket.git
-  tag:      eventlog-socket-0.1.2.0
+  tag:      eventlog-socket-0.1.3.0
   subdir:   eventlog-socket
 
 flag debug
@@ -98,6 +102,8 @@
   default-extensions: LambdaCase
   c-sources:
     cbits/eventlog_socket/error.c
+    cbits/eventlog_socket/hooks.c
+    cbits/eventlog_socket/hsapi.c
     cbits/eventlog_socket/string.c
     cbits/eventlog_socket/worker.c
     cbits/eventlog_socket/write_buffer.c
diff --git a/include/eventlog_socket.h b/include/eventlog_socket.h
--- a/include/eventlog_socket.h
+++ b/include/eventlog_socket.h
@@ -182,6 +182,7 @@
 /// to determine the host name for a TCP/IPv4 socket.
 ///
 /// @since 0.1.2.0
+// NOTE: Keep in sync with eventlogSocketEnvInetHost in Socket.hsc.
 #define EVENTLOG_SOCKET_ENV_INET_HOST "GHC_EVENTLOG_INET_HOST"
 
 /// @brief
@@ -189,6 +190,7 @@
 /// to determine the port number for a TCP/IPv4 socket.
 ///
 /// @since 0.1.2.0
+// NOTE: Keep in sync with eventlogSocketEnvInetPort in Socket.hsc.
 #define EVENTLOG_SOCKET_ENV_INET_PORT "GHC_EVENTLOG_INET_PORT"
 
 /// @brief
@@ -476,6 +478,47 @@
 ///
 /// @since 0.1.2.0
 EventlogSocketStatus eventlog_socket_control_status(void);
+
+/******************************************************************************
+ * Start/End Hooks
+ ******************************************************************************/
+
+/// @brief An @c eventlog-socket hook.
+typedef enum {
+  /// @brief The post-startEventLogging hook is guaranteed to be called *after*
+  /// each time that the `EventLogSocketWriter` is attached and event logging is
+  /// started.
+  ///
+  /// If the `EventLogSocketWriter` is attached in a C main, prior to GHC RTS
+  /// initialisation, then @c eventlog-socket waits to call this hook for the
+  /// first time until the GHC RTS is initialised.
+  EVENTLOG_SOCKET_HOOK_POST_START_EVENT_LOGGING,
+  /// @brief The pre-endEventLogging hook is guaranteed to be called *before*
+  /// each time that the `EventLogSocketWriter` is detached and event logging is
+  /// ended.
+  EVENTLOG_SOCKET_HOOK_PRE_END_EVENT_LOGGING,
+} EventlogSocketHook;
+
+/// @brief An @c eventlog-socket hook handler.
+///
+/// @since 0.1.3.0
+typedef void EventlogSocketHookHandler(const void *hook_data);
+
+/// @brief Register a new hook handler.
+///
+/// @return Upon successful completion, this function registers the command and
+/// returns a status with code @c EVENTLOG_SOCKET_OK.
+///
+/// @return If a system error occurs, this function returns a status with code
+/// @c EVENTLOG_SOCKET_ERR_SYS and the error code set to indicate the error.
+///
+/// @note The @p hook_handler should not use the functions from this header.
+///
+/// @since 0.1.3.0
+EventlogSocketStatus
+eventlog_socket_register_hook(EventlogSocketHook hook,
+                              EventlogSocketHookHandler *hook_handler,
+                              const void *hook_data);
 
 /******************************************************************************
  * Control Commands
diff --git a/src/GHC/Eventlog/Socket.hsc b/src/GHC/Eventlog/Socket.hsc
--- a/src/GHC/Eventlog/Socket.hsc
+++ b/src/GHC/Eventlog/Socket.hsc
@@ -45,6 +45,14 @@
     fromEnv,
     EventlogSocketAddrError(..),
 
+    -- ** Hooks #hooks#
+    Hook (
+        HookPostStartEventLogging,
+        HookPreEndEventLogging
+    ),
+    HookHandler,
+    registerHook,
+
     -- ** Control commands #control_commands#
     Namespace,
     CommandId(..),
@@ -81,9 +89,8 @@
 import GHC.Enum (toEnumError)
 import System.IO.Unsafe (unsafePerformIO)
 
-#include <eventlog_socket/macros.h>
 #include <eventlog_socket.h>
-#include <string.h>
+#include <eventlog_socket/hsapi.h>
 
 --------------------------------------------------------------------------------
 -- High-level API
@@ -250,18 +257,27 @@
 --------------------------------------------------------------------------------
 -- Environment variables
 
-eventlogSocketEnvUnixPath :: String
-eventlogSocketEnvUnixPath = #{const_str EVENTLOG_SOCKET_ENV_UNIX_PATH}
+{- |
+The name of the environment variable used by `eventlog_socket_from_env`
+to determine the host name for a TCP/IPv4 socket.
 
+@since 0.1.2.0
+-}
 eventlogSocketEnvInetHost :: String
-eventlogSocketEnvInetHost = #{const_str EVENTLOG_SOCKET_ENV_INET_HOST}
+-- NOTE: Keep in sync with EVENTLOG_SOCKET_ENV_INET_HOST in eventlog_socketh.h.
+eventlogSocketEnvInetHost = "GHC_EVENTLOG_INET_HOST"
 
-eventlogSocketEnvInetPort :: String
-eventlogSocketEnvInetPort = #{const_str EVENTLOG_SOCKET_ENV_INET_PORT}
 
-eventlogSocketEnvWait :: String
-eventlogSocketEnvWait = #{const_str EVENTLOG_SOCKET_ENV_WAIT}
+{- |
+The name of the environment variable used by `eventlog_socket_from_env`
+to determine the port number for a TCP/IPv4 socket.
 
+@since 0.1.2.0
+-}
+eventlogSocketEnvInetPort :: String
+-- NOTE: Keep in sync with EVENTLOG_SOCKET_ENV_INET_PORT in eventlog_socketh.h.
+eventlogSocketEnvInetPort = "GHC_EVENTLOG_INET_PORT"
+
 --------------------------------------------------------------------------------
 -- Errors
 
@@ -305,7 +321,7 @@
                 <> " was not set."
 
 {- |
-Test the current status of the worker thread. If it has failed, throw an `IOException`.
+Test the current status of the worker thread. If it has failed, throw an `Control.Exception.IOException`.
 
 @since 0.1.2.0
 -}
@@ -314,7 +330,7 @@
     throwEventlogSocketStatusAsIOException =<< workerStatus
 
 {- |
-Test the current status of the control thread. If it has failed, throw an `IOException`.
+Test the current status of the control thread. If it has failed, throw an `Control.Exception.IOException`.
 
 @since 0.1.2.0
 -}
@@ -347,7 +363,7 @@
 {- |
 Internal helper.
 
-Throw an `EventlogSocketStatus` as an `IOException`.
+Throw an t`EventlogSocketStatus` as an `Control.Exception.IOException`.
 -}
 throwEventlogSocketStatusAsIOException :: EventlogSocketStatus -> IO ()
 throwEventlogSocketStatusAsIOException ess =
@@ -357,7 +373,7 @@
 {- |
 Internal helper.
 
-Throw an `EventlogSocketStatus` as an `IOException`.
+Throw an t`EventlogSocketStatus` as an `Control.Exception.IOException`.
 
 __Warning__: This function _still_ throws an error if the status code is `EVENTLOG_SOCKET_OK`.
 -}
@@ -369,6 +385,98 @@
     throwIO $ userError str
 
 --------------------------------------------------------------------------------
+-- Hooks API
+--------------------------------------------------------------------------------
+
+{- |
+The type of @eventlog-socket@ hooks.
+
+@since 0.1.3.0
+-}
+newtype
+    {-# CTYPE "eventlog_socket.h" "EventlogSocketHook" #-}
+    Hook = Hook
+        { unHook :: #{type EventlogSocketHook}
+        }
+        deriving (Eq, Show, Storable)
+
+{- |
+The hook that runs /after/ @startEventLogging@ is called.
+
+@since 0.1.3.0
+-}
+pattern HookPostStartEventLogging :: Hook
+pattern HookPostStartEventLogging = Hook #{const EVENTLOG_SOCKET_HOOK_POST_START_EVENT_LOGGING}
+
+{- |
+The hook that runs /before/ @endEventLogging@ is called.
+
+@since 0.1.3.0
+-}
+pattern HookPreEndEventLogging :: Hook
+pattern HookPreEndEventLogging = Hook #{const EVENTLOG_SOCKET_HOOK_PRE_END_EVENT_LOGGING}
+
+{-# COMPLETE
+    HookPostStartEventLogging,
+    HookPreEndEventLogging #-}
+
+{- |
+The type of hook handlers.
+
+The hook handler is evaluated once each time the control socket receives a request for the associated hook.
+
+__Warning__: The hook handler /must not/ call back into the @eventlog-socket@ API.
+
+@since 0.1.3.0
+-}
+type HookHandler = IO ()
+
+{- |
+Register an @eventlog-socket@ hook.
+
+__Warning__: Hooks cannot be unregistered and will be kept in memory until program exit.
+
+@since 0.1.3.0
+-}
+registerHook ::
+    -- | The hook.
+    Hook ->
+    -- | The hook handler.
+    HookHandler ->
+    IO ()
+registerHook hook hookHandler = do
+    -- Wrap the Haskell hook handler for the C API
+    let c_hookHandler hookDataPtr =
+            assert (hookDataPtr == nullPtr) $
+            hookHandler
+
+    bracketOnError (makeHookHandlerFunPtr c_hookHandler) freeHaskellFunPtr $ \c_hookHandlerPtr -> do
+
+        -- Try to register the command:
+        status <-
+            -- Allocate space for the return status:
+            allocaBytes #{size EventlogSocketStatus} $ \essPtr -> do
+
+                -- Try to register the command:
+                eventlog_socket_register_hook
+                    essPtr
+                    hook
+                    c_hookHandlerPtr
+                    nullPtr
+
+                -- Marshal the return status:
+                peekEventlogSocketStatus essPtr
+
+        -- Handle the return status:
+        case essStatusCode status of
+            -- The return status is OK.
+            EVENTLOG_SOCKET_OK -> pure ()
+
+            -- The remaining errors are all system errors:
+            _otherwise ->
+                vacuous $ withEventlogSocketStatus status throwEventlogSocketStatus
+
+--------------------------------------------------------------------------------
 -- Control Commands API
 --------------------------------------------------------------------------------
 
@@ -898,53 +1006,23 @@
 --------------------------------------------------------------------------------
 -- eventlog_socket_start
 
-foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_start"
+foreign import capi safe "eventlog_socket/hsapi.h es_hsapi_start"
     eventlog_socket_start ::
         Ptr EventlogSocketStatus ->
         Ptr EventlogSocketAddr ->
         Ptr EventlogSocketOpts ->
         IO ()
 
-#{def
-  HIDDEN void eventlog_socket_wrap_start(
-    EventlogSocketStatus *eventlog_socket_status,
-    EventlogSocketAddr *eventlog_socket_addr,
-    EventlogSocketOpts *eventlog_socket_opts
-  )
-  {
-    const EventlogSocketStatus status = eventlog_socket_start(
-      eventlog_socket_addr,
-      eventlog_socket_opts
-    );
-    memcpy(eventlog_socket_status, &status, sizeof(EventlogSocketStatus));
-  }
-}
-
 --------------------------------------------------------------------------------
 -- eventlog_socket_from_env
 
-foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_from_env"
+foreign import capi safe "eventlog_socket/hsapi.h es_hsapi_from_env"
     eventlog_socket_from_env ::
         Ptr EventlogSocketStatus ->
         Ptr EventlogSocketAddr ->
         Ptr EventlogSocketOpts ->
         IO ()
 
-#{def
-  HIDDEN void eventlog_socket_wrap_from_env(
-    EventlogSocketStatus *eventlog_socket_status,
-    EventlogSocketAddr *eventlog_socket_addr,
-    EventlogSocketOpts *eventlog_socket_opts
-  )
-  {
-    const EventlogSocketStatus status = eventlog_socket_from_env(
-      eventlog_socket_addr,
-      eventlog_socket_opts
-    );
-    memcpy(eventlog_socket_status, &status, sizeof(EventlogSocketStatus));
-  }
-}
-
 --------------------------------------------------------------------------------
 -- eventlog_socket_wait
 
@@ -954,56 +1032,42 @@
 --------------------------------------------------------------------------------
 -- eventlog_socket_strerror
 
-foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_strerror"
+foreign import capi safe "eventlog_socket/hsapi.h es_hsapi_strerror"
     eventlog_socket_strerror ::
         Ptr EventlogSocketStatus ->
         IO CString
 
-#{def
-  HIDDEN char *eventlog_socket_wrap_strerror(
-    EventlogSocketStatus *eventlog_socket_status
-  )
-  {
-    return eventlog_socket_strerror(*eventlog_socket_status);
-  }
-}
+--------------------------------------------------------------------------------
+-- eventlog_socket_register_hook
 
+foreign import capi safe "eventlog_socket/hsapi.h es_hsapi_register_hook"
+    eventlog_socket_register_hook ::
+        Ptr EventlogSocketStatus ->
+        Hook ->
+        FunPtr (Ptr a -> IO ()) ->
+        Ptr a ->
+        IO ()
+
+foreign import ccall "wrapper"
+    makeHookHandlerFunPtr ::
+        (Ptr a -> IO ()) ->
+        IO (FunPtr (Ptr a -> IO ()))
+
 --------------------------------------------------------------------------------
 -- eventlog_socket_worker_status
 
-foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_worker_status"
+foreign import capi safe "eventlog_socket/hsapi.h es_hsapi_worker_status"
     eventlog_socket_worker_status ::
         Ptr EventlogSocketStatus ->
         IO ()
-
-#{def
-  HIDDEN void eventlog_socket_wrap_worker_status(
-    EventlogSocketStatus *eventlog_socket_status_out
-  )
-  {
-    const EventlogSocketStatus eventlog_socket_status = eventlog_socket_worker_status();
-    memcpy(eventlog_socket_status_out, &eventlog_socket_status, sizeof(EventlogSocketStatus));
-  }
-}
-
 --------------------------------------------------------------------------------
 -- eventlog_socket_control_status
 
-foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_control_status"
+foreign import capi safe "eventlog_socket/hsapi.h es_hsapi_control_status"
     eventlog_socket_control_status ::
         Ptr EventlogSocketStatus ->
         IO ()
 
-#{def
-  HIDDEN void eventlog_socket_wrap_control_status(
-    EventlogSocketStatus *eventlog_socket_status_out
-  )
-  {
-    const EventlogSocketStatus eventlog_socket_status = eventlog_socket_control_status();
-    memcpy(eventlog_socket_status_out, &eventlog_socket_status, sizeof(EventlogSocketStatus));
-  }
-}
-
 --------------------------------------------------------------------------------
 -- eventlog_socket_control_strnamespace
 
@@ -1018,7 +1082,7 @@
 --------------------------------------------------------------------------------
 -- eventlog_socket_control_register_namespace
 
-foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_control_register_namespace"
+foreign import capi safe "eventlog_socket/hsapi.h es_hsapi_control_register_namespace"
     eventlog_socket_control_register_namespace ::
         Ptr EventlogSocketStatus ->
         ( #{type uint8_t} ) ->
@@ -1026,26 +1090,10 @@
         Ptr (Ptr Namespace) ->
         IO ()
 
-#{def
-  HIDDEN void eventlog_socket_wrap_control_register_namespace(
-    EventlogSocketStatus *eventlog_socket_status,
-    uint8_t eventlog_socket_namespace_len,
-    char eventlog_socket_namespace[eventlog_socket_namespace_len],
-    EventlogSocketControlNamespace **eventlog_socket_namespace_out
-  ) {
-    const EventlogSocketStatus status = eventlog_socket_control_register_namespace(
-      eventlog_socket_namespace_len,
-      eventlog_socket_namespace,
-      eventlog_socket_namespace_out
-    );
-    memcpy(eventlog_socket_status, &status, sizeof(EventlogSocketStatus));
-  }
-}
-
 --------------------------------------------------------------------------------
 -- eventlog_socket_control_register_command
 
-foreign import capi safe "GHC/Eventlog/Socket_hsc.h eventlog_socket_wrap_control_register_command"
+foreign import capi safe "eventlog_socket/hsapi.h es_hsapi_control_register_command"
     eventlog_socket_control_register_command ::
         Ptr EventlogSocketStatus ->
         Ptr Namespace ->
@@ -1058,21 +1106,3 @@
     makeCommandHandlerFunPtr ::
         (Ptr Namespace -> CommandId -> Ptr a -> IO ()) ->
         IO (FunPtr (Ptr Namespace -> CommandId -> Ptr a -> IO ()))
-
-#{def
-  HIDDEN void eventlog_socket_wrap_control_register_command(
-    EventlogSocketStatus *eventlog_socket_status,
-    EventlogSocketControlNamespace *eventlog_socket_namespace,
-    EventlogSocketControlCommandId eventlog_socket_command_id,
-    EventlogSocketControlCommandHandler eventlog_socket_command_handler,
-    const void *eventlog_socket_command_data
-  ) {
-    const EventlogSocketStatus status = eventlog_socket_control_register_command(
-      eventlog_socket_namespace,
-      eventlog_socket_command_id,
-      eventlog_socket_command_handler,
-      eventlog_socket_command_data
-    );
-    memcpy(eventlog_socket_status, &status, sizeof(EventlogSocketStatus));
-  }
-}
