diff --git a/examples/chat.c b/examples/chat.c
index 7af622688bdeb733bd2b3bfbfb0d6f831cbc0bba..16c1fb24e7c54a6fa84516e04422e63dc02bd746 100644
--- a/examples/chat.c
+++ b/examples/chat.c
@@ -325,34 +325,25 @@ static void redirect_to_ssl(struct mg_connection *conn,
   }
 }
 
-static void *event_handler(enum mg_event event,
-                           struct mg_connection *conn) {
+static int begin_request_handler(struct mg_connection *conn) {
   const struct mg_request_info *request_info = mg_get_request_info(conn);
-  void *processed = "yes";
-
-  if (event == MG_NEW_REQUEST) {
-    if (!request_info->is_ssl) {
-      redirect_to_ssl(conn, request_info);
-    } else if (!is_authorized(conn, request_info)) {
-      redirect_to_login(conn, request_info);
-    } else if (strcmp(request_info->uri, authorize_url) == 0) {
-      authorize(conn, request_info);
-    } else if (strcmp(request_info->uri, "/ajax/get_messages") == 0) {
-      ajax_get_messages(conn, request_info);
-    } else if (strcmp(request_info->uri, "/ajax/send_message") == 0) {
-      ajax_send_message(conn, request_info);
-    } else {
-      // No suitable handler found, mark as not processed. Mongoose will
-      // try to serve the request.
-      processed = NULL;
-    }
-  } else if (event == MG_EVENT_LOG) {
-    printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data);
-    processed = NULL;
+  int processed = 1;
+
+  if (!request_info->is_ssl) {
+    redirect_to_ssl(conn, request_info);
+  } else if (!is_authorized(conn, request_info)) {
+    redirect_to_login(conn, request_info);
+  } else if (strcmp(request_info->uri, authorize_url) == 0) {
+    authorize(conn, request_info);
+  } else if (strcmp(request_info->uri, "/ajax/get_messages") == 0) {
+    ajax_get_messages(conn, request_info);
+  } else if (strcmp(request_info->uri, "/ajax/send_message") == 0) {
+    ajax_send_message(conn, request_info);
   } else {
-    processed = NULL;
+    // No suitable handler found, mark as not processed. Mongoose will
+    // try to serve the request.
+    processed = 0;
   }
-
   return processed;
 }
 
@@ -365,6 +356,7 @@ static const char *options[] = {
 };
 
 int main(void) {
+  struct mg_callbacks callbacks;
   struct mg_context *ctx;
 
   // Initialize random number generator. It will be used later on for
@@ -372,7 +364,9 @@ int main(void) {
   srand((unsigned) time(0));
 
   // Setup and start Mongoose
-  if ((ctx = mg_start(&event_handler, NULL, options)) == NULL) {
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.begin_request = begin_request_handler;
+  if ((ctx = mg_start(&callbacks, NULL, options)) == NULL) {
     printf("%s\n", "Cannot start chat server, fatal exit");
     exit(EXIT_FAILURE);
   }
diff --git a/examples/hello.c b/examples/hello.c
index 5eee899cff24669956a4015fd65e8e08ae5a4901..680a0122f0606c34e8c05eab820866e927706124 100644
--- a/examples/hello.c
+++ b/examples/hello.c
@@ -2,35 +2,49 @@
 #include <string.h>
 #include "mongoose.h"
 
-static void *callback(enum mg_event event,
-                      struct mg_connection *conn) {
+// This function will be called by mongoose on every new request.
+static int begin_request_handler(struct mg_connection *conn) {
   const struct mg_request_info *request_info = mg_get_request_info(conn);
+  char content[100];
 
-  if (event == MG_NEW_REQUEST) {
-    char content[1024];
-    int content_length = snprintf(content, sizeof(content),
-                                  "Hello from mongoose! Remote port: %d",
-                                  request_info->remote_port);
-    mg_printf(conn,
-              "HTTP/1.1 200 OK\r\n"
-              "Content-Type: text/plain\r\n"
-              "Content-Length: %d\r\n"        // Always set Content-Length
-              "\r\n"
-              "%s",
-              content_length, content);
-    // Mark as processed
-    return "";
-  } else {
-    return NULL;
-  }
+  // Prepare the message we're going to send
+  int content_length = snprintf(content, sizeof(content),
+                                "Hello from mongoose! Remote port: %d",
+                                request_info->remote_port);
+
+  // Send HTTP reply to the client
+  mg_printf(conn,
+            "HTTP/1.1 200 OK\r\n"
+            "Content-Type: text/plain\r\n"
+            "Content-Length: %d\r\n"        // Always set Content-Length
+            "\r\n"
+            "%s",
+            content_length, content);
+
+  // Returning non-zero tells mongoose that our function has replied to
+  // the client, and mongoose should not send client any more data.
+  return 1;
 }
 
 int main(void) {
   struct mg_context *ctx;
+  struct mg_callbacks callbacks;
+
+  // List of options. Last element must be NULL.
   const char *options[] = {"listening_ports", "8080", NULL};
 
-  ctx = mg_start(&callback, NULL, options);
-  getchar();  // Wait until user hits "enter"
+  // Prepare callbacks structure. We have only one callback, the rest are NULL.
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.begin_request = begin_request_handler;
+
+  // Start the web server.
+  ctx = mg_start(&callbacks, NULL, options);
+
+  // Wait until user hits "enter". Server is running in separate thread.
+  // Navigating to http://localhost:8080 will invoke begin_request_handler().
+  getchar();
+
+  // Stop the server.
   mg_stop(ctx);
 
   return 0;
diff --git a/examples/post.c b/examples/post.c
index f30cc0eefa2c6ed0fdf97c551eff50fd510ebc7c..1c0a476c725764e83b69993b43c177aa5466484e 100644
--- a/examples/post.c
+++ b/examples/post.c
@@ -10,50 +10,45 @@ static const char *html_form =
   "<input type=\"submit\" />"
   "</form></body></html>";
 
-static void *callback(enum mg_event event,
-                      struct mg_connection *conn) {
+static int begin_request_handler(struct mg_connection *conn) {
   const struct mg_request_info *ri = mg_get_request_info(conn);
-
-  if (event == MG_NEW_REQUEST) {
-    if (!strcmp(ri->uri, "/handle_post_request")) {
-      // User has submitted a form, show submitted data and a variable value
-      char post_data[1024],
-           input1[sizeof(post_data)], input2[sizeof(post_data)];
-      int post_data_len;
-
-      // Read POST data
-      post_data_len = mg_read(conn, post_data, sizeof(post_data));
-
-      // Parse form data. input1 and input2 are guaranteed to be NUL-terminated
-      mg_get_var(post_data, post_data_len, "input_1", input1, sizeof(input1));
-      mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2));
-
-      mg_printf(conn, "HTTP/1.0 200 OK\r\n"
-                "Content-Type: text/plain\r\n\r\n"
-                "Submitted data: [%.*s]\n"
-                "Submitted data length: %d bytes\n"
-                "input_1: [%s]\n"
-                "input_2: [%s]\n",
-                post_data_len, post_data, post_data_len, input1, input2);
-    } else {
-      // Show HTML form.
-      mg_printf(conn, "HTTP/1.0 200 OK\r\n"
-                "Content-Length: %d\r\n"
-                "Content-Type: text/html\r\n\r\n%s",
-                (int) strlen(html_form), html_form);
-    }
-    // Mark as processed
-    return "";
+  char post_data[1024], input1[sizeof(post_data)], input2[sizeof(post_data)];
+  int post_data_len;
+
+  if (!strcmp(ri->uri, "/handle_post_request")) {
+    // User has submitted a form, show submitted data and a variable value
+    post_data_len = mg_read(conn, post_data, sizeof(post_data));
+
+    // Parse form data. input1 and input2 are guaranteed to be NUL-terminated
+    mg_get_var(post_data, post_data_len, "input_1", input1, sizeof(input1));
+    mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2));
+
+    // Send reply to the client, showing submitted form values.
+    mg_printf(conn, "HTTP/1.0 200 OK\r\n"
+              "Content-Type: text/plain\r\n\r\n"
+              "Submitted data: [%.*s]\n"
+              "Submitted data length: %d bytes\n"
+              "input_1: [%s]\n"
+              "input_2: [%s]\n",
+              post_data_len, post_data, post_data_len, input1, input2);
   } else {
-    return NULL;
+    // Show HTML form.
+    mg_printf(conn, "HTTP/1.0 200 OK\r\n"
+              "Content-Length: %d\r\n"
+              "Content-Type: text/html\r\n\r\n%s",
+              (int) strlen(html_form), html_form);
   }
+  return 1;  // Mark request as processed
 }
 
 int main(void) {
   struct mg_context *ctx;
   const char *options[] = {"listening_ports", "8080", NULL};
+  struct mg_callbacks callbacks;
 
-  ctx = mg_start(&callback, NULL, options);
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.begin_request = begin_request_handler;
+  ctx = mg_start(&callbacks, NULL, options);
   getchar();  // Wait until user hits "enter"
   mg_stop(ctx);
 
diff --git a/examples/upload.c b/examples/upload.c
index 0a7da2c321edb8c72f1463f2229d2d97ab71e10f..599da50e1b4aef05cb65181ba98719049b4a7ece 100644
--- a/examples/upload.c
+++ b/examples/upload.c
@@ -17,42 +17,44 @@ typedef __int64 int64_t;
 
 #include "mongoose.h"
 
-static void *callback(enum mg_event event, struct mg_connection *conn) {
-  if (event == MG_NEW_REQUEST) {
-    if (!strcmp(mg_get_request_info(conn)->uri, "/handle_post_request")) {
-      mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n");
-      mg_upload(conn, "/tmp");
-    } else {
-      // Show HTML form. Make sure it has enctype="multipart/form-data" attr.
-      static const char *html_form =
-        "<html><body>Upload example."
-        "<form method=\"POST\" action=\"/handle_post_request\" "
-        "  enctype=\"multipart/form-data\">"
-        "<input type=\"file\" name=\"file\" /> <br/>"
-        "<input type=\"submit\" value=\"Upload\" />"
-        "</form></body></html>";
-
-      mg_printf(conn, "HTTP/1.0 200 OK\r\n"
-                "Content-Length: %d\r\n"
-                "Content-Type: text/html\r\n\r\n%s",
-                (int) strlen(html_form), html_form);
-    }
-    // Mark as processed
-    return "";
-  } else if (event == MG_UPLOAD) {
-    mg_printf(conn, "Saved [%s]", mg_get_request_info(conn)->ev_data);
+static int begin_request_handler(struct mg_connection *conn) {
+  if (!strcmp(mg_get_request_info(conn)->uri, "/handle_post_request")) {
+    mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n");
+    mg_upload(conn, "/tmp");
+  } else {
+    // Show HTML form. Make sure it has enctype="multipart/form-data" attr.
+    static const char *html_form =
+      "<html><body>Upload example."
+      "<form method=\"POST\" action=\"/handle_post_request\" "
+      "  enctype=\"multipart/form-data\">"
+      "<input type=\"file\" name=\"file\" /> <br/>"
+      "<input type=\"submit\" value=\"Upload\" />"
+      "</form></body></html>";
+
+    mg_printf(conn, "HTTP/1.0 200 OK\r\n"
+              "Content-Length: %d\r\n"
+              "Content-Type: text/html\r\n\r\n%s",
+              (int) strlen(html_form), html_form);
   }
 
-  return NULL;
+  // Mark request as processed
+  return 1;
+}
+
+static void upload_handler(struct mg_connection *conn, const char *path) {
+  mg_printf(conn, "Saved [%s]", path);
 }
 
 int main(void) {
   struct mg_context *ctx;
   const char *options[] = {"listening_ports", "8080", NULL};
+  struct mg_callbacks callbacks;
 
-  ctx = mg_start(&callback, NULL, options);
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.begin_request = begin_request_handler;
+  callbacks.upload = upload_handler;
+  ctx = mg_start(&callbacks, NULL, options);
   getchar();  // Wait until user hits "enter"
-  pause();
   mg_stop(ctx);
 
   return 0;
diff --git a/examples/websocket.c b/examples/websocket.c
index 509f8bf8c838f66940635f5a5230e633baef5c98..d073a83d1c452c888264046ec786e2238cbd6dc6 100644
--- a/examples/websocket.c
+++ b/examples/websocket.c
@@ -5,69 +5,70 @@
 #include <string.h>
 #include "mongoose.h"
 
-static void *callback(enum mg_event event, struct mg_connection *conn) {
-  if (event == MG_WEBSOCKET_READY) {
-    unsigned char buf[40];
-    buf[0] = 0x81;
-    buf[1] = snprintf((char *) buf + 2, sizeof(buf) - 2, "%s", "server ready");
-    mg_write(conn, buf, 2 + buf[1]);
-    return "";  // MG_WEBSOCKET_READY return value is ignored
-  } else if (event == MG_WEBSOCKET_MESSAGE) {
-    unsigned char buf[200], reply[200];
-    int n, i, mask_len, xor, msg_len, len;
+static void websocket_ready_handler(struct mg_connection *conn) {
+  unsigned char buf[40];
+  buf[0] = 0x81;
+  buf[1] = snprintf((char *) buf + 2, sizeof(buf) - 2, "%s", "server ready");
+  mg_write(conn, buf, 2 + buf[1]);
+}
+
+static int websocket_data_handler(struct mg_connection *conn) {
+  unsigned char buf[200], reply[200];
+  int n, i, mask_len, xor, msg_len, len;
 
-    // Read message from the client.
-    // Accept only small (<126 bytes) messages.
-    len = 0;
-    msg_len = mask_len = 0;
-    for (;;) {
-      if ((n = mg_read(conn, buf + len, sizeof(buf) - len)) <= 0) {
-        return "";  // Read error, close websocket
+  // Read message from the client.
+  // Accept only small (<126 bytes) messages.
+  len = 0;
+  msg_len = mask_len = 0;
+  for (;;) {
+    if ((n = mg_read(conn, buf + len, sizeof(buf) - len)) <= 0) {
+      return 0;  // Read error, close websocket
+    }
+    len += n;
+    if (len >= 2) {
+      msg_len = buf[1] & 127;
+      mask_len = (buf[1] & 128) ? 4 : 0;
+      if (msg_len > 125) {
+        return 0; // Message is too long, close websocket
       }
-      len += n;
-      if (len >= 2) {
-        msg_len = buf[1] & 127;
-        mask_len = (buf[1] & 128) ? 4 : 0;
-        if (msg_len > 125) {
-          return ""; // Message is too long, close websocket
-        }
-        // If we've buffered the whole message, exit the loop
-        if (len >= 2 + mask_len + msg_len) {
-          break;
-        }
+      // If we've buffered the whole message, exit the loop
+      if (len >= 2 + mask_len + msg_len) {
+        break;
       }
     }
+  }
 
-    // Prepare frame
-    reply[0] = 0x81;  // text, FIN set
-    reply[1] = msg_len;
+  // Prepare frame
+  reply[0] = 0x81;  // text, FIN set
+  reply[1] = msg_len;
 
-    // Copy message from request to reply, applying the mask if required.
-    for (i = 0; i < msg_len; i++) {
-      xor = mask_len == 0 ? 0 : buf[2 + (i % 4)];
-      reply[i + 2] = buf[i + 2 + mask_len] ^ xor;
-    }
+  // Copy message from request to reply, applying the mask if required.
+  for (i = 0; i < msg_len; i++) {
+    xor = mask_len == 0 ? 0 : buf[2 + (i % 4)];
+    reply[i + 2] = buf[i + 2 + mask_len] ^ xor;
+  }
 
-    // Echo the message back to the client
-    mg_write(conn, reply, 2 + msg_len);
+  // Echo the message back to the client
+  mg_write(conn, reply, 2 + msg_len);
 
-    // Return non-NULL means stoping websocket conversation.
-    // Close the conversation if client has sent us "exit" string.
-    return memcmp(reply + 2, "exit", 4) == 0 ? "" : NULL;
-  } else {
-    return NULL;
-  }
+  // Returnint zero means stoping websocket conversation.
+  // Close the conversation if client has sent us "exit" string.
+  return memcmp(reply + 2, "exit", 4);
 }
 
 int main(void) {
   struct mg_context *ctx;
+  struct mg_callbacks callbacks;
   const char *options[] = {
     "listening_ports", "8080",
     "document_root", "websocket_html_root",
     NULL
   };
 
-  ctx = mg_start(&callback, NULL, options);
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.websocket_ready = websocket_ready_handler;
+  callbacks.websocket_data = websocket_data_handler;
+  ctx = mg_start(&callbacks, NULL, options);
   getchar();  // Wait until user hits "enter"
   mg_stop(ctx);
 
diff --git a/main.c b/main.c
index 6a44ea2b9a24e35a35500e530cc9410a36e214e8..8d8f47f919e88619103aacd333b4d44b18fbaa31 100644
--- a/main.c
+++ b/main.c
@@ -266,17 +266,14 @@ static void init_server_name(void) {
            mg_version());
 }
 
-static void *mongoose_callback(enum mg_event ev, struct mg_connection *conn) {
-  if (ev == MG_EVENT_LOG) {
-    printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data);
-  }
-
-  // Returning NULL marks request as not handled, signalling mongoose to
-  // proceed with handling it.
-  return NULL;
+static int log_message(const struct mg_connection *conn, const char *message) {
+  (void) conn;
+  printf("%s\n", message);
+  return 0;
 }
 
 static void start_mongoose(int argc, char *argv[]) {
+  struct mg_callbacks callbacks;
   char *options[MAX_OPTIONS];
   int i;
 
@@ -302,7 +299,9 @@ static void start_mongoose(int argc, char *argv[]) {
   signal(SIGINT, signal_handler);
 
   /* Start Mongoose */
-  ctx = mg_start(&mongoose_callback, NULL, (const char **) options);
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.log_message = &log_message;
+  ctx = mg_start(&callbacks, NULL, (const char **) options);
   for (i = 0; options[i] != NULL; i++) {
     free(options[i]);
   }
diff --git a/mongoose.c b/mongoose.c
index 6816f9fab7dcc4435ac046e8b6f5a9f9468dc014..3725490403366f579c3c90eb92d925bb2e07d720 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -466,11 +466,11 @@ static const char *config_options[] = {
 #define ENTRIES_PER_CONFIG_OPTION 3
 
 struct mg_context {
-  volatile int stop_flag;       // Should we stop event loop
-  SSL_CTX *ssl_ctx;             // SSL context
-  char *config[NUM_OPTIONS];    // Mongoose configuration parameters
-  mg_callback_t user_callback;  // User-defined callback function
-  void *user_data;              // User-defined data
+  volatile int stop_flag;         // Should we stop event loop
+  SSL_CTX *ssl_ctx;               // SSL context
+  char *config[NUM_OPTIONS];      // Mongoose configuration parameters
+  struct mg_callbacks callbacks;  // User-defined callback function
+  void *user_data;                // User-defined data
 
   struct socket *listening_sockets;
   int num_listening_sockets;
@@ -512,20 +512,12 @@ const char **mg_get_valid_option_names(void) {
   return config_options;
 }
 
-static void *call_user(struct mg_connection *conn, enum mg_event event) {
-  if (conn != NULL && conn->ctx != NULL) {
-    conn->request_info.user_data = conn->ctx->user_data;
-  }
-  return conn == NULL || conn->ctx == NULL || conn->ctx->user_callback == NULL ?
-    NULL : conn->ctx->user_callback(event, conn);
-}
-
 static int is_file_in_memory(struct mg_connection *conn, const char *path,
                              struct file *filep) {
-  conn->request_info.ev_data = (void *) path;
-  if ((filep->membuf = call_user(conn, MG_OPEN_FILE)) != NULL) {
-    filep->size = (long) conn->request_info.ev_data;
-  }
+  size_t size = 0;
+  filep->membuf = conn->ctx->callbacks.open_file == NULL ? NULL :
+    conn->ctx->callbacks.open_file(conn, path, &size);
+  filep->size = size;
   return filep->membuf != NULL;
 }
 
@@ -610,8 +602,8 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) {
   // Do not lock when getting the callback value, here and below.
   // I suppose this is fine, since function cannot disappear in the
   // same way string option can.
-  conn->request_info.ev_data = buf;
-  if (call_user(conn, MG_EVENT_LOG) == NULL) {
+  if (conn->ctx->callbacks.log_message == NULL ||
+      conn->ctx->callbacks.log_message(conn, buf) == 0) {
     fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
       fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
 
@@ -634,7 +626,6 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) {
       fclose(fp);
     }
   }
-  conn->request_info.ev_data = NULL;
 }
 
 // Return fake connection structure. Used for logging, if connection
@@ -917,31 +908,27 @@ static void send_http_error(struct mg_connection *conn, int status,
                             const char *reason, const char *fmt, ...) {
   char buf[MG_BUF_LEN];
   va_list ap;
-  int len;
+  int len = 0;
 
   conn->status_code = status;
-  conn->request_info.ev_data = (void *) (long) status;
-  if (call_user(conn, MG_HTTP_ERROR) == NULL) {
-    buf[0] = '\0';
-    len = 0;
-
-    // Errors 1xx, 204 and 304 MUST NOT send a body
-    if (status > 199 && status != 204 && status != 304) {
-      len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason);
-      buf[len++] = '\n';
-
-      va_start(ap, fmt);
-      len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap);
-      va_end(ap);
-    }
-    DEBUG_TRACE(("[%s]", buf));
+  buf[0] = '\0';
 
-    mg_printf(conn, "HTTP/1.1 %d %s\r\n"
-              "Content-Length: %d\r\n"
-              "Connection: %s\r\n\r\n", status, reason, len,
-              suggest_connection_header(conn));
-    conn->num_bytes_sent += mg_printf(conn, "%s", buf);
+  // Errors 1xx, 204 and 304 MUST NOT send a body
+  if (status > 199 && status != 204 && status != 304) {
+    len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason);
+    buf[len++] = '\n';
+
+    va_start(ap, fmt);
+    len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap);
+    va_end(ap);
   }
+  DEBUG_TRACE(("[%s]", buf));
+
+  mg_printf(conn, "HTTP/1.1 %d %s\r\n"
+            "Content-Length: %d\r\n"
+            "Connection: %s\r\n\r\n", status, reason, len,
+            suggest_connection_header(conn));
+  conn->num_bytes_sent += mg_printf(conn, "%s", buf);
 }
 
 #if defined(_WIN32) && !defined(__SYMBIAN32__)
@@ -2609,7 +2596,7 @@ static int scan_directory(struct mg_connection *conn, const char *dir,
       // print_dir_entry(). memset is required only if mg_stat()
       // fails. For more details, see
       // http://code.google.com/p/mongoose/issues/detail?id=79
-      // mg_stat will memset the whole struct file with zeroes.
+      memset(&de.file, 0, sizeof(de.file));
       mg_stat(conn, path, &de.file);
 
       de.file_name = dp->d_name;
@@ -3797,7 +3784,8 @@ static void read_websocket(struct mg_connection *conn) {
     }
 
     if (conn->content_len > 0) {
-      if (call_user(conn, MG_WEBSOCKET_MESSAGE) != NULL) {
+      if (conn->ctx->callbacks.websocket_data != NULL &&
+          conn->ctx->callbacks.websocket_data(conn) == 0) {
         break;  // Callback signalled to exit
       }
       discard_len = conn->content_len > body_len ?
@@ -3819,13 +3807,15 @@ static void read_websocket(struct mg_connection *conn) {
 static void handle_websocket_request(struct mg_connection *conn) {
   if (strcmp(mg_get_header(conn, "Sec-WebSocket-Version"), "13") != 0) {
     send_http_error(conn, 426, "Upgrade Required", "%s", "Upgrade Required");
-  } else if (call_user(conn, MG_WEBSOCKET_CONNECT) != NULL) {
-    // Callback has returned non-NULL, do not proceed with handshake
+  } else if (conn->ctx->callbacks.websocket_connect != NULL &&
+             conn->ctx->callbacks.websocket_connect(conn) != 0) {
+    // Callback has returned non-zero, do not proceed with handshake
   } else {
     send_websocket_handshake(conn);
-    call_user(conn, MG_WEBSOCKET_READY);
+    if (conn->ctx->callbacks.websocket_ready != NULL) {
+      conn->ctx->callbacks.websocket_ready(conn);
+    }
     read_websocket(conn);
-    call_user(conn, MG_WEBSOCKET_CLOSE);
   }
 }
 
@@ -4035,8 +4025,9 @@ static void handle_lsp_request(struct mg_connection *conn, const char *path,
   } else {
     // We're not sending HTTP headers here, Lua page must do it.
     prepare_lua_environment(conn, L);
-    conn->request_info.ev_data = L;
-    call_user(conn, MG_INIT_LUA);
+    if (conn->ctx->callbacks.init_lua != NULL) {
+      conn->ctx->callbacks.init_lua(conn, L);
+    }
     lsp(conn, filep->membuf == NULL ? p : filep->membuf, filep->size, L);
   }
 
@@ -4135,8 +4126,9 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) {
           fwrite(buf, 1, i, fp);
           fflush(fp);
           num_uploaded_files++;
-          conn->request_info.ev_data = (void *) path;
-          call_user(conn, MG_UPLOAD);
+          if (conn->ctx->callbacks.upload != NULL) {
+            conn->ctx->callbacks.upload(conn, path);
+          }
           memmove(buf, &buf[i + bl], len - (i + bl));
           len -= i + bl;
           break;
@@ -4203,7 +4195,8 @@ static void handle_request(struct mg_connection *conn) {
                                 get_remote_ip(conn), ri->uri);
 
   DEBUG_TRACE(("%s", ri->uri));
-  if (call_user(conn, MG_NEW_REQUEST) != NULL) {
+  if (conn->ctx->callbacks.begin_request != NULL &&
+      conn->ctx->callbacks.begin_request(conn)) {
     // Do nothing, callback has served the request
   } else if (!conn->client.is_ssl && conn->client.ssl_redir &&
       (ssl_index = get_first_ssl_listener_index(conn->ctx)) > -1) {
@@ -4550,8 +4543,8 @@ static int set_ssl_option(struct mg_context *ctx) {
 
   // If user callback returned non-NULL, that means that user callback has
   // set up certificate itself. In this case, skip sertificate setting.
-  fc(ctx)->request_info.ev_data = ctx->ssl_ctx;
-  if (call_user(fc(ctx), MG_INIT_SSL) == NULL &&
+  if ((ctx->callbacks.init_ssl == NULL ||
+       !ctx->callbacks.init_ssl(ctx->ssl_ctx)) &&
       (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0 ||
        SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0)) {
     cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error());
@@ -4608,7 +4601,7 @@ static int set_acl_option(struct mg_context *ctx) {
 }
 
 static void reset_per_request_attributes(struct mg_connection *conn) {
-  conn->path_info = conn->request_info.ev_data = NULL;
+  conn->path_info = NULL;
   conn->num_bytes_sent = conn->consumed_content = 0;
   conn->status_code = -1;
   conn->must_close = conn->request_len = conn->throttle = 0;
@@ -4811,8 +4804,9 @@ static void process_new_connection(struct mg_connection *conn) {
 
     if (ebuf[0] == '\0') {
       handle_request(conn);
-      conn->request_info.ev_data = (void *) (long) conn->status_code;
-      call_user(conn, MG_REQUEST_COMPLETE);
+      if (conn->ctx->callbacks.end_request != NULL) {
+        conn->ctx->callbacks.end_request(conn, conn->status_code);
+      }
       log_access(conn);
     }
     if (ri->remote_user != NULL) {
@@ -5087,7 +5081,8 @@ void mg_stop(struct mg_context *ctx) {
 #endif // _WIN32
 }
 
-struct mg_context *mg_start(mg_callback_t user_callback, void *user_data,
+struct mg_context *mg_start(const struct mg_callbacks *callbacks,
+                            void *user_data,
                             const char **options) {
   struct mg_context *ctx;
   const char *name, *value, *default_value;
@@ -5104,7 +5099,7 @@ struct mg_context *mg_start(mg_callback_t user_callback, void *user_data,
   if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) {
     return NULL;
   }
-  ctx->user_callback = user_callback;
+  ctx->callbacks = *callbacks;
   ctx->user_data = user_data;
 
   while (options && (name = *options++) != NULL) {
diff --git a/mongoose.h b/mongoose.h
index 8ec7271a2f14983031ddc4ab5444db9a82c661e8..20904f8a0fc837fd9aa8dfd2ff929346338fc946 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -42,13 +42,13 @@ struct mg_request_info {
   long remote_ip;             // Client's IP address
   int remote_port;            // Client's port
   int is_ssl;                 // 1 if SSL-ed, 0 if not
-  int num_headers;            // Number of headers
+  void *user_data;            // User data pointer passed to mg_start()
+
+  int num_headers;            // Number of HTTP headers
   struct mg_header {
     const char *name;         // HTTP header name
     const char *value;        // HTTP header value
   } http_headers[64];         // Maximum 64 headers
-  void *user_data;            // User data pointer passed to mg_start()
-  void *ev_data;              // Event-specific data pointer
 };
 
 
@@ -56,126 +56,23 @@ struct mg_request_info {
 // which callbacks to invoke. For detailed description, see
 // https://github.com/valenok/mongoose/blob/master/UserManual.md
 struct mg_callbacks {
-  int  (*request_start)(struct mg_connection *);
-  void (*request_done)(struct mg_connection *, int reply_status_code);
-  int  (*log_message)(struct mg_connection *, const char *message);
+  int  (*begin_request)(struct mg_connection *);
+  void (*end_request)(const struct mg_connection *, int reply_status_code);
+  int  (*log_message)(const struct mg_connection *, const char *message);
   int  (*init_ssl)(void *ssl_context);
-  void (*websocket_connect)(struct mg_connection *);
+  int (*websocket_connect)(const struct mg_connection *);
   void (*websocket_ready)(struct mg_connection *);
   int  (*websocket_data)(struct mg_connection *);
-  void (*websocket_close)(struct mg_connection *);
-  void (*open_file)(struct mg_connection *, char **data, size_t *data_len);
+  const char * (*open_file)(const struct mg_connection *,
+                             const char *path, size_t *data_len);
   void (*init_lua)(struct mg_connection *, void *lua_context);
   void (*upload)(struct mg_connection *, const char *file_name);
 };
 
-
-// Various events on which user-defined callback function is called by Mongoose.
-enum mg_event {
-  // New HTTP request has arrived from the client.
-  // If callback returns non-NULL, Mongoose stops handling current request.
-  // ev_data contains NULL.
-  MG_NEW_REQUEST,
-
-  // Mongoose has finished handling the request.
-  // Callback return value is ignored.
-  // ev_data contains integer HTTP status code:
-  //  int http_reply_status_code = (long) request_info->ev_data;
-  MG_REQUEST_COMPLETE,
-
-  // HTTP error must be returned to the client.
-  // If callback returns non-NULL, Mongoose stops handling error.
-  // ev_data contains HTTP error code:
-  //  int http_reply_status_code = (long) request_info->ev_data;
-  MG_HTTP_ERROR,
-
-  // Mongoose logs a message.
-  // If callback returns non-NULL, Mongoose stops handling that event.
-  // ev_data contains a message to be logged:
-  //   const char *log_message = request_info->ev_data;
-  MG_EVENT_LOG,
-
-  // SSL initialization, sent before certificate setup.
-  // If callback returns non-NULL, Mongoose does not set up certificates.
-  // ev_data contains server's OpenSSL context:
-  //   SSL_CTX *ssl_context = request_info->ev_data;
-  MG_INIT_SSL,
-
-  // Sent on HTTP connect, before websocket handshake.
-  // If user callback returns NULL, then mongoose proceeds
-  // with handshake, otherwise it closes the connection.
-  // ev_data contains NULL.
-  MG_WEBSOCKET_CONNECT,
-
-  // Handshake has been successfully completed.
-  // Callback's return value is ignored.
-  // ev_data contains NULL.
-  MG_WEBSOCKET_READY,
-
-  // Incoming message from the client, data could be read with mg_read().
-  // If user callback returns non-NULL, mongoose closes the websocket.
-  // ev_data contains NULL.
-  MG_WEBSOCKET_MESSAGE,
-
-  // Client has closed the connection.
-  // Callback's return value is ignored.
-  // ev_data contains NULL.
-  MG_WEBSOCKET_CLOSE,
-
-  // Mongoose tries to open file.
-  // If callback returns non-NULL, Mongoose will not try to open it, but
-  // will use the returned value as a pointer to the file data. This allows
-  // for example to serve files from memory.
-  // ev_data contains file path, including document root path.
-  // Upon return, ev_data should return file size,  which should be a long int.
-  //
-  //   const char *file_name = request_info->ev_data;
-  //   if (strcmp(file_name, "foo.txt") == 0) {
-  //     request_info->ev_data = (void *) (long) 4;
-  //     return "data";
-  //   }
-  //   return NULL;
-  //
-  // Note that this even is sent multiple times during one request. Each
-  // time mongoose tries to open or stat the file, this event is sent, e.g.
-  // for opening .htpasswd file, stat-ting requested file, opening requested
-  // file, etc.
-  MG_OPEN_FILE,
-
-  // Mongoose initializes Lua server page. Sent only if Lua support is enabled.
-  // Callback's return value is ignored.
-  // ev_data contains lua_State pointer.
-  MG_INIT_LUA,
-
-  // Mongoose has uploaded file to a temporary directory.
-  // Callback's return value is ignored.
-  // ev_data contains NUL-terminated file name.
-  MG_UPLOAD,
-};
-
-
-// Prototype for the user-defined function. Mongoose calls this function
-// on every MG_* event.
-//
-// Parameters:
-//   event: which event has been triggered.
-//   conn: opaque connection handler. Could be used to read, write data to the
-//         client, etc. See functions below that have "mg_connection *" arg.
-//
-// Return:
-//   If handler returns non-NULL, that means that handler has processed the
-//   request by sending appropriate HTTP reply to the client. Mongoose treats
-//   the request as served.
-//   If handler returns NULL, that means that handler has not processed
-//   the request. Handler must not send any data to the client in this case.
-//   Mongoose proceeds with request handling as if nothing happened.
-typedef void *(*mg_callback_t)(enum mg_event event, struct mg_connection *conn);
-
-
 // Start web server.
 //
 // Parameters:
-//   callback: user defined event handling function or NULL.
+//   callbacks: mg_callbacks structure with user-defined callbacks.
 //   options: NULL terminated list of option_name, option_value pairs that
 //            specify Mongoose configuration parameters.
 //
@@ -197,8 +94,9 @@ typedef void *(*mg_callback_t)(enum mg_event event, struct mg_connection *conn);
 //
 // Return:
 //   web server context, or NULL on error.
-struct mg_context *mg_start(mg_callback_t callback, void *user_data,
-                            const char **options);
+struct mg_context *mg_start(const struct mg_callbacks *callbacks,
+                            void *user_data,
+                            const char **configuration_options);
 
 
 // Stop the web server.
diff --git a/test/test.pl b/test/test.pl
index d41b337dad525a80f6050a4a4e820e2d427666e9..513b27a014ebcb37672cc65530c1ce570c5b93c8 100644
--- a/test/test.pl
+++ b/test/test.pl
@@ -426,7 +426,7 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
   do_PUT_test();
   kill_spawned_child();
   do_unit_test();
-  do_embedded_test();
+#do_embedded_test();
 }
 
 sub do_PUT_test {
diff --git a/test/unit_test.c b/test/unit_test.c
index 11dbe339679a1130f58d0e8d084d394486338b79..273dbadd0203c969d41c0256c853f236bf440a7c 100644
--- a/test/unit_test.c
+++ b/test/unit_test.c
@@ -189,44 +189,61 @@ static const char *inmemory_file_data = "hi there";
 static const char *upload_filename = "upload_test.txt";
 static const char *upload_ok_message = "upload successful";
 
-static void *event_handler(enum mg_event event, struct mg_connection *conn) {
-  const struct mg_request_info *request_info = mg_get_request_info(conn);
+static const char *open_file_cb(const struct mg_connection *conn,
+                                const char *path, size_t *size) {
+  (void) conn;
+  if (!strcmp(path, "./blah")) {
+    *size = strlen(inmemory_file_data);
+    return inmemory_file_data;
+  }
+  return NULL;
+}
+
+static void upload_cb(struct mg_connection *conn, const char *path) {
+  char *p1, *p2;
+  int len1, len2;
+
+  ASSERT(!strcmp(path, "./upload_test.txt"));
+  ASSERT((p1 = read_file("mongoose.c", &len1)) != NULL);
+  ASSERT((p2 = read_file(path, &len2)) != NULL);
+  ASSERT(len1 == len2);
+  ASSERT(memcmp(p1, p2, len1) == 0);
+  free(p1), free(p2);
+  remove(upload_filename);
+
+  mg_printf(conn, "HTTP/1.0 200 OK\r\nContent-Length: %d\r\n\r\n%s",
+            (int) strlen(upload_ok_message), upload_ok_message);
+}
+
+static int begin_request_handler_cb(struct mg_connection *conn) {
+  const struct mg_request_info *ri = mg_get_request_info(conn);
 
-  if (event == MG_NEW_REQUEST && !strcmp(request_info->uri, "/data")) {
+  if (!strcmp(ri->uri, "/data")) {
     mg_printf(conn, "HTTP/1.1 200 OK\r\n"
               "Content-Length: %d\r\n"
               "Content-Type: text/plain\r\n\r\n"
               "%s", (int) strlen(fetch_data), fetch_data);
-    return "";
-  } else if (event == MG_NEW_REQUEST && !strcmp(request_info->uri, "/upload")) {
+    return 1;
+  }
+
+  if (!strcmp(ri->uri, "/upload")) {
     ASSERT(mg_upload(conn, ".") == 1);
-  } else if (event == MG_OPEN_FILE) {
-    const char *path = request_info->ev_data;
-    if (strcmp(path, "./blah") == 0) {
-      mg_get_request_info(conn)->ev_data =
-        (void *) (long) strlen(inmemory_file_data);
-      return (void *) inmemory_file_data;
-    }
-  } else if (event == MG_EVENT_LOG) {
-  } else if (event == MG_UPLOAD) {
-    char *p1, *p2;
-    int len1, len2;
-
-    ASSERT(!strcmp((char *) request_info->ev_data, "./upload_test.txt"));
-    ASSERT((p1 = read_file("mongoose.c", &len1)) != NULL);
-    ASSERT((p2 = read_file(upload_filename, &len2)) != NULL);
-    ASSERT(len1 == len2);
-    ASSERT(memcmp(p1, p2, len1) == 0);
-    free(p1), free(p2);
-    remove(upload_filename);
-
-    mg_printf(conn, "HTTP/1.0 200 OK\r\nContent-Length: %d\r\n\r\n%s",
-              (int) strlen(upload_ok_message), upload_ok_message);
   }
 
-  return NULL;
+  return 0;
+}
+
+static int log_message_cb(const struct mg_connection *conn, const char *msg) {
+  (void) conn;
+  printf("%s\n", msg);
+  return 0;
 }
 
+static const struct mg_callbacks CALLBACKS = {
+  &begin_request_handler_cb, NULL, &log_message_cb, NULL, NULL, NULL, NULL,
+  &open_file_cb, NULL, &upload_cb
+};
+
 static const char *OPTIONS[] = {
   "document_root", ".",
   "listening_ports", LISTENING_ADDR,
@@ -252,7 +269,7 @@ static void test_mg_download(void) {
   struct mg_connection *conn;
   struct mg_context *ctx;
 
-  ASSERT((ctx = mg_start(event_handler, NULL, OPTIONS)) != NULL);
+  ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
 
   ASSERT(mg_download(NULL, port, 0, ebuf, sizeof(ebuf), "%s", "") == NULL);
   ASSERT(mg_download("localhost", 0, 0, ebuf, sizeof(ebuf), "%s", "") == NULL);
@@ -323,7 +340,7 @@ static void test_mg_upload(void) {
   char ebuf[100], buf[20], *file_data, *post_data = NULL;
   int file_len, post_data_len;
 
-  ASSERT((ctx = mg_start(event_handler, NULL, OPTIONS)) != NULL);
+  ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
   ASSERT((file_data = read_file("mongoose.c", &file_len)) != NULL);
   post_data_len = alloc_printf(&post_data, 0,
                                        "--%s\r\n"
@@ -462,22 +479,6 @@ static void test_lua(void) {
 }
 #endif
 
-static void *user_data_tester(enum mg_event event, struct mg_connection *conn) {
-  struct mg_request_info *ri = mg_get_request_info(conn);
-  ASSERT(ri->user_data == (void *) 123);
-  ASSERT(event == MG_NEW_REQUEST || event == MG_INIT_SSL);
-  return NULL;
-}
-
-static void test_user_data(void) {
-  struct mg_context *ctx;
-
-  ASSERT((ctx = mg_start(user_data_tester, (void *) 123, OPTIONS)) != NULL);
-  ASSERT(ctx->user_data == (void *) 123);
-  call_user(fc(ctx), MG_NEW_REQUEST);
-  mg_stop(ctx);
-}
-
 static void test_mg_stat(void) {
   static struct mg_context ctx;
   struct file file = STRUCT_FILE_INITIALIZER;
@@ -529,7 +530,7 @@ static void test_request_replies(void) {
     {NULL, NULL},
   };
 
-  ASSERT((ctx = mg_start(event_handler, NULL, OPTIONS)) != NULL);
+  ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
   for (i = 0; tests[i].request != NULL; i++) {
     ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
                                tests[i].request)) != NULL);
@@ -549,7 +550,6 @@ int __cdecl main(void) {
   test_mg_get_var();
   test_set_throttle();
   test_next_option();
-  test_user_data();
   test_mg_stat();
   test_skip_quoted();
   test_mg_upload();