diff --git a/app/include/zmk/split/bluetooth/central.h b/app/include/zmk/split/bluetooth/central.h
index 5e9e09ff..973a4e8b 100644
--- a/app/include/zmk/split/bluetooth/central.h
+++ b/app/include/zmk/split/bluetooth/central.h
@@ -8,8 +8,8 @@
 #include <zmk/hid_indicators_types.h>
 #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
 
-int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *binding,
-                                 struct zmk_behavior_binding_event event, bool state);
+int zmk_split_invoke_behavior(uint8_t source, struct zmk_behavior_binding *binding,
+                              struct zmk_behavior_binding_event event, bool state);
 
 #if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
 
diff --git a/app/include/zmk/split/central.h b/app/include/zmk/split/central.h
index afdc9cd2..45205d78 100644
--- a/app/include/zmk/split/central.h
+++ b/app/include/zmk/split/central.h
@@ -8,9 +8,12 @@
 
 #include <zmk/events/position_state_changed.h>
 #include <zmk/events/sensor_event.h>
+#include <zmk/split/service.h>
 
 void zmk_position_state_change_handle(struct zmk_position_state_changed *ev);
 
 #if ZMK_KEYMAP_HAS_SENSORS
 void zmk_sensor_event_handle(struct zmk_sensor_event *ev);
 #endif
+
+void send_split_run_impl(struct zmk_split_run_behavior_payload_wrapper *payload_wrapper);
diff --git a/app/include/zmk/split/service.h b/app/include/zmk/split/service.h
index 169a78c4..98ce16cb 100644
--- a/app/include/zmk/split/service.h
+++ b/app/include/zmk/split/service.h
@@ -31,6 +31,11 @@ struct zmk_split_run_behavior_payload {
     char behavior_dev[ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN];
 } __packed;
 
+struct zmk_split_run_behavior_payload_wrapper {
+    uint8_t source;
+    struct zmk_split_run_behavior_payload payload;
+};
+
 int zmk_split_position_pressed(uint8_t position);
 int zmk_split_position_released(uint8_t position);
 int zmk_split_sensor_triggered(uint8_t sensor_index,
diff --git a/app/src/keymap.c b/app/src/keymap.c
index 94bd1204..e7e75212 100644
--- a/app/src/keymap.c
+++ b/app/src/keymap.c
@@ -214,7 +214,7 @@ int zmk_keymap_apply_position_state(uint8_t source, int layer, uint32_t position
         if (source == ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL) {
             return invoke_locally(&binding, event, pressed);
         } else {
-            return zmk_split_bt_invoke_behavior(source, &binding, event, pressed);
+            return zmk_split_invoke_behavior(source, &binding, event, pressed);
         }
 #else
         return invoke_locally(&binding, event, pressed);
@@ -222,7 +222,7 @@ int zmk_keymap_apply_position_state(uint8_t source, int layer, uint32_t position
     case BEHAVIOR_LOCALITY_GLOBAL:
 #if ZMK_BLE_IS_CENTRAL
         for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
-            zmk_split_bt_invoke_behavior(i, &binding, event, pressed);
+            zmk_split_invoke_behavior(i, &binding, event, pressed);
         }
 #endif
         return invoke_locally(&binding, event, pressed);
diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c
index c6ca2b27..1d42ed45 100644
--- a/app/src/split/bluetooth/central.c
+++ b/app/src/split/bluetooth/central.c
@@ -714,89 +714,24 @@ static struct bt_conn_cb conn_callbacks = {
     .disconnected = split_central_disconnected,
 };
 
-K_THREAD_STACK_DEFINE(split_central_split_run_q_stack,
-                      CONFIG_ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_STACK_SIZE);
-
-struct k_work_q split_central_split_run_q;
-
-struct zmk_split_run_behavior_payload_wrapper {
-    uint8_t source;
-    struct zmk_split_run_behavior_payload payload;
-};
-
-K_MSGQ_DEFINE(zmk_split_central_split_run_msgq,
-              sizeof(struct zmk_split_run_behavior_payload_wrapper),
-              CONFIG_ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_QUEUE_SIZE, 4);
-
-void split_central_split_run_callback(struct k_work *work) {
-    struct zmk_split_run_behavior_payload_wrapper payload_wrapper;
-
-    LOG_DBG("");
-
-    while (k_msgq_get(&zmk_split_central_split_run_msgq, &payload_wrapper, K_NO_WAIT) == 0) {
-        if (peripherals[payload_wrapper.source].state != PERIPHERAL_SLOT_STATE_CONNECTED) {
-            LOG_ERR("Source not connected");
-            continue;
-        }
-        if (!peripherals[payload_wrapper.source].run_behavior_handle) {
-            LOG_ERR("Run behavior handle not found");
-            continue;
-        }
-
-        int err = bt_gatt_write_without_response(
-            peripherals[payload_wrapper.source].conn,
-            peripherals[payload_wrapper.source].run_behavior_handle, &payload_wrapper.payload,
-            sizeof(struct zmk_split_run_behavior_payload), true);
-
-        if (err) {
-            LOG_ERR("Failed to write the behavior characteristic (err %d)", err);
-        }
+void send_split_run_impl(struct zmk_split_run_behavior_payload_wrapper *payload_wrapper) {
+    if (peripherals[payload_wrapper->source].state != PERIPHERAL_SLOT_STATE_CONNECTED) {
+        LOG_ERR("Source not connected");
+        return;
+    }
+    if (!peripherals[payload_wrapper->source].run_behavior_handle) {
+        LOG_ERR("Run behavior handle not found");
+        return;
     }
-}
 
-K_WORK_DEFINE(split_central_split_run_work, split_central_split_run_callback);
+    int err = bt_gatt_write_without_response(
+        peripherals[payload_wrapper->source].conn,
+        peripherals[payload_wrapper->source].run_behavior_handle, &payload_wrapper->payload,
+        sizeof(struct zmk_split_run_behavior_payload), true);
 
-static int
-split_bt_invoke_behavior_payload(struct zmk_split_run_behavior_payload_wrapper payload_wrapper) {
-    LOG_DBG("");
-
-    int err = k_msgq_put(&zmk_split_central_split_run_msgq, &payload_wrapper, K_MSEC(100));
     if (err) {
-        switch (err) {
-        case -EAGAIN: {
-            LOG_WRN("Consumer message queue full, popping first message and queueing again");
-            struct zmk_split_run_behavior_payload_wrapper discarded_report;
-            k_msgq_get(&zmk_split_central_split_run_msgq, &discarded_report, K_NO_WAIT);
-            return split_bt_invoke_behavior_payload(payload_wrapper);
-        }
-        default:
-            LOG_WRN("Failed to queue behavior to send (%d)", err);
-            return err;
-        }
+        LOG_ERR("Failed to write the behavior characteristic (err %d)", err);
     }
-
-    k_work_submit_to_queue(&split_central_split_run_q, &split_central_split_run_work);
-
-    return 0;
-};
-
-int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *binding,
-                                 struct zmk_behavior_binding_event event, bool state) {
-    struct zmk_split_run_behavior_payload payload = {.data = {
-                                                         .param1 = binding->param1,
-                                                         .param2 = binding->param2,
-                                                         .position = event.position,
-                                                         .state = state ? 1 : 0,
-                                                     }};
-    const size_t payload_dev_size = sizeof(payload.behavior_dev);
-    if (strlcpy(payload.behavior_dev, binding->behavior_dev, payload_dev_size) >=
-        payload_dev_size) {
-        LOG_ERR("Truncated behavior label %s to %s before invoking peripheral behavior",
-                binding->behavior_dev, payload.behavior_dev);
-    }
-
-    struct zmk_split_run_behavior_payload_wrapper wrapper = {.source = source, .payload = payload};
-    return split_bt_invoke_behavior_payload(wrapper);
 }
 
 #if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
@@ -853,9 +788,6 @@ static struct settings_handler ble_central_settings_handler = {
 #endif // IS_ENABLED(CONFIG_SETTINGS)
 
 static int zmk_split_bt_central_init(void) {
-    k_work_queue_start(&split_central_split_run_q, split_central_split_run_q_stack,
-                       K_THREAD_STACK_SIZEOF(split_central_split_run_q_stack),
-                       CONFIG_ZMK_BLE_THREAD_PRIORITY, NULL);
     bt_conn_cb_register(&conn_callbacks);
 
 #if IS_ENABLED(CONFIG_SETTINGS)
diff --git a/app/src/split/central.c b/app/src/split/central.c
index 96f379a2..0c45b493 100644
--- a/app/src/split/central.c
+++ b/app/src/split/central.c
@@ -7,9 +7,13 @@
 #include <zephyr/types.h>
 #include <zephyr/init.h>
 
+#include <zmk/stdlib.h>
+#include <zmk/behavior.h>
 #include <zmk/event_manager.h>
 #include <zmk/events/position_state_changed.h>
 #include <zmk/events/sensor_event.h>
+#include <zmk/split/central.h>
+#include <zmk/split/service.h>
 
 #include <zephyr/logging/log.h>
 LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
@@ -51,3 +55,76 @@ void zmk_sensor_event_handle(struct zmk_sensor_event *ev) {
     k_work_submit(&peripheral_sensor_event_work);
 }
 #endif /* ZMK_KEYMAP_HAS_SENSORS */
+
+K_THREAD_STACK_DEFINE(split_central_split_run_q_stack,
+                      CONFIG_ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_STACK_SIZE);
+
+struct k_work_q split_central_split_run_q;
+
+K_MSGQ_DEFINE(zmk_split_central_split_run_msgq,
+              sizeof(struct zmk_split_run_behavior_payload_wrapper),
+              CONFIG_ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_QUEUE_SIZE, 4);
+
+void split_central_split_run_callback(struct k_work *work) {
+    struct zmk_split_run_behavior_payload_wrapper payload_wrapper;
+
+    LOG_DBG("");
+
+    while (k_msgq_get(&zmk_split_central_split_run_msgq, &payload_wrapper, K_NO_WAIT) == 0) {
+        send_split_run_impl(&payload_wrapper);
+    }
+}
+
+K_WORK_DEFINE(split_central_split_run_work, split_central_split_run_callback);
+
+static int
+split_invoke_behavior_payload(struct zmk_split_run_behavior_payload_wrapper payload_wrapper) {
+    LOG_DBG("");
+
+    int err = k_msgq_put(&zmk_split_central_split_run_msgq, &payload_wrapper, K_MSEC(100));
+    if (err) {
+        switch (err) {
+        case -EAGAIN: {
+            LOG_WRN("Consumer message queue full, popping first message and queueing again");
+            struct zmk_split_run_behavior_payload_wrapper discarded_report;
+            k_msgq_get(&zmk_split_central_split_run_msgq, &discarded_report, K_NO_WAIT);
+            return split_invoke_behavior_payload(payload_wrapper);
+        }
+        default:
+            LOG_WRN("Failed to queue behavior to send (%d)", err);
+            return err;
+        }
+    }
+
+    k_work_submit_to_queue(&split_central_split_run_q, &split_central_split_run_work);
+
+    return 0;
+};
+
+int zmk_split_invoke_behavior(uint8_t source, struct zmk_behavior_binding *binding,
+                              struct zmk_behavior_binding_event event, bool state) {
+    struct zmk_split_run_behavior_payload payload = {.data = {
+                                                         .param1 = binding->param1,
+                                                         .param2 = binding->param2,
+                                                         .position = event.position,
+                                                         .state = state ? 1 : 0,
+                                                     }};
+    const size_t payload_dev_size = sizeof(payload.behavior_dev);
+    if (strlcpy(payload.behavior_dev, binding->behavior_dev, payload_dev_size) >=
+        payload_dev_size) {
+        LOG_ERR("Truncated behavior label %s to %s before invoking peripheral behavior",
+                binding->behavior_dev, payload.behavior_dev);
+    }
+
+    struct zmk_split_run_behavior_payload_wrapper wrapper = {.source = source, .payload = payload};
+    return split_invoke_behavior_payload(wrapper);
+}
+
+static int zmk_split_central_init(void) {
+    k_work_queue_start(&split_central_split_run_q, split_central_split_run_q_stack,
+                       K_THREAD_STACK_SIZEOF(split_central_split_run_q_stack),
+                       CONFIG_ZMK_BLE_THREAD_PRIORITY, NULL);
+    return 0;
+}
+
+SYS_INIT(zmk_split_central_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY);