diff --git a/Makefile.pseudomodules b/Makefile.pseudomodules
index 438fa415ad90cc9e8ff7c3b359cd07b921676906..27976a1dab9b386be6ad4d44c15d65990712c214 100644
--- a/Makefile.pseudomodules
+++ b/Makefile.pseudomodules
@@ -12,6 +12,7 @@ PSEUDOMODULES += gnrc_ipv6_router
 PSEUDOMODULES += gnrc_ipv6_router_default
 PSEUDOMODULES += gnrc_netdev_default
 PSEUDOMODULES += gnrc_neterr
+PSEUDOMODULES += gnrc_netapi_callbacks
 PSEUDOMODULES += gnrc_netapi_mbox
 PSEUDOMODULES += gnrc_pktbuf
 PSEUDOMODULES += gnrc_sixlowpan_border_router_default
diff --git a/sys/include/net/gnrc/netapi.h b/sys/include/net/gnrc/netapi.h
index 63ea497b7820bd80643c52782ffa9ff81cba057e..25dc2a41dd731e6ad7156478d9e7133fb0ae4ac5 100644
--- a/sys/include/net/gnrc/netapi.h
+++ b/sys/include/net/gnrc/netapi.h
@@ -36,6 +36,21 @@
  * USEMODULE += gnrc_netapi_mbox
  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  * @}
+ *
+ * @defgroup    net_gnrc_netapi_callbacks   Callback extension
+ * @ingroup     net_gnrc_netapi
+ * @brief       Callback extension for @ref net_gnrc_netapi
+ * @{
+ * @details The submodule `gnrc_netapi_callbacks` provides an extension for
+ *          callbacks to run GNRC thread-less.
+ *
+ * To use, add the module `gnrc_netapi_callbacks` to the `USEMODULE` macro in
+ * your application's Makefile:
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.mk}
+ * USEMODULE += gnrc_netapi_callbacks
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * @}
  * @author      Martine Lenders <mlenders@inf.fu-berlin.de>
  * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
  */
diff --git a/sys/include/net/gnrc/netreg.h b/sys/include/net/gnrc/netreg.h
index d040f332c95cb0a2fba4cb0519c01140b88ce23a..e078a51d9c2d1e3cedd2a862f87a3d7c9ece9c58 100644
--- a/sys/include/net/gnrc/netreg.h
+++ b/sys/include/net/gnrc/netreg.h
@@ -35,13 +35,16 @@
 extern "C" {
 #endif
 
-#if defined(MODULE_GNRC_NETAPI_MBOX) || defined(DOXYGEN)
+#if defined(MODULE_GNRC_NETAPI_MBOX) || defined(MODULE_GNRC_NETAPI_CALLBACKS) || \
+    defined(DOXYGEN)
 typedef enum {
     GNRC_NETREG_TYPE_DEFAULT = 0,
 #if defined(MODULE_GNRC_NETAPI_MBOX) || defined(DOXYGEN)
     GNRC_NETREG_TYPE_MBOX,
 #endif
+#if defined(MODULE_GNRC_NETAPI_CALLBACKS) || defined(DOXYGEN)
     GNRC_NETREG_TYPE_CB,
+#endif
 } gnrc_netreg_type_t;
 #endif
 
@@ -77,7 +80,7 @@ typedef enum {
  *                      for the netreg entry
  * @param[in] mbox      Target @ref core_mbox "mailbox" for the registry entry
  *
- * @note    Only available with @ref net_gnrc_netreg_extra.
+ * @note    Only available with @ref net_gnrc_netapi_mbox.
  *
  * @return  An initialized netreg entry
  */
@@ -86,6 +89,48 @@ typedef enum {
                                                        { .mbox = mbox } }
 #endif
 
+#if defined(MODULE_GNRC_NETAPI_CALLBACKS) || defined(DOXYGEN)
+/**
+ * @brief   Initializes a netreg entry statically with callback
+ *
+ * @param[in] demux_ctx The @ref gnrc_netreg_entry_t::demux_ctx "demux context"
+ *                      for the netreg entry
+ * @param[in] cb        Target callback for the registry entry
+ *
+ * @note    Only available with @ref net_gnrc_netapi_callbacks.
+ *
+ * @return  An initialized netreg entry
+ */
+#define GNRC_NETREG_ENTRY_INIT_CB(demux_ctx, cbd)   { NULL, demux_ctx, \
+                                                      GNRC_NETREG_TYPE_CB, \
+                                                      { .cbd = cbd } }
+
+/**
+ * @brief   Packet handler callback for netreg entries with callback.
+ *
+ * @pre `cmd` $\in$ { @ref GNRC_NETAPI_MSG_TYPE_RCV, @ref GNRC_NETAPI_MSG_TYPE_SND }
+ *
+ * @note    Only available with @ref net_gnrc_netapi_callbacks.
+ *
+ * @param[in] cmd   @ref net_gnrc_netapi command type. Must be either
+ *                  @ref GNRC_NETAPI_MSG_TYPE_SND or
+ *                  @ref GNRC_NETAPI_MSG_TYPE_RCV
+ * @param[in] pkt   The packet to handle.
+ * @param[in] ctx   Application context.
+ */
+typedef void (*gnrc_netreg_entry_cb_t)(uint16_t cmd, gnrc_pktsnip_t *pkt,
+                                       void *ctx);
+
+/**
+ * @brief   Callback + Context descriptor
+ * @note    Only available with @ref net_gnrc_netapi_callbacks.
+ */
+typedef struct {
+    gnrc_netreg_entry_cb_t cb;  /**< the callback */
+    void *ctx;                  /**< application context for the callback */
+} gnrc_netreg_entry_cbd_t;
+#endif
+
 /**
  * @brief   Entry to the @ref net_gnrc_netreg
  */
@@ -105,7 +150,8 @@ typedef struct gnrc_netreg_entry {
      *          ports in UDP/TCP, or similar.
      */
     uint32_t demux_ctx;
-#if defined(MODULE_GNRC_NETAPI_MBOX) || defined(DOXYGEN)
+#if defined(MODULE_GNRC_NETAPI_MBOX) || defined(MODULE_GNRC_NETAPI_CALLBACKS) || \
+    defined(DOXYGEN)
     /**
      * @brief   Type of the registry entry
      *
@@ -124,6 +170,15 @@ typedef struct gnrc_netreg_entry {
          */
         mbox_t *mbox;
 #endif
+
+#if defined(MODULE_GNRC_NETAPI_CALLBACKS) || defined(DOXYGEN)
+        /**
+         * @brief   Target callback for the registry entry
+         *
+         * @note    Only available with @ref net_gnrc_netapi_callbacks.
+         */
+        gnrc_netreg_entry_cbd_t *cbd;
+#endif
     } target;                   /**< Target for the registry entry */
 } gnrc_netreg_entry_t;
 
@@ -147,7 +202,7 @@ static inline void gnrc_netreg_entry_init_pid(gnrc_netreg_entry_t *entry,
 {
     entry->next = NULL;
     entry->demux_ctx = demux_ctx;
-#ifdef MODULE_GNRC_NETAPI_MBOX
+#if defined(MODULE_GNRC_NETAPI_MBOX) || defined(MODULE_GNRC_NETAPI_CALLBACKS)
     entry->type = GNRC_NETREG_TYPE_DEFAULT;
 #endif
     entry->target.pid = pid;
@@ -175,6 +230,28 @@ static inline void gnrc_netreg_entry_init_mbox(gnrc_netreg_entry_t *entry,
 }
 #endif
 
+#if defined(MODULE_GNRC_NETAPI_CALLBACKS) || defined(DOXYGEN)
+/**
+ * @brief   Initializes a netreg entry dynamically with callback
+ *
+ * @param[out] entry    A netreg entry
+ * @param[in] demux_ctx The @ref gnrc_netreg_entry_t::demux_ctx "demux context"
+ *                      for the netreg entry
+ * @param[in] mbox      Target callback for the registry entry
+ *
+ * @note    Only available with @ref net_gnrc_netapi_callbacks.
+ */
+static inline void gnrc_netreg_entry_init_cb(gnrc_netreg_entry_t *entry,
+                                             uint32_t demux_ctx,
+                                             gnrc_netreg_entry_cbd_t *cbd)
+{
+    entry->next = NULL;
+    entry->demux_ctx = demux_ctx;
+    entry->type = GNRC_NETREG_TYPE_CB;
+    entry->target.cbd = cbd;
+}
+#endif
+
 /**
  * @brief   Registers a thread to the registry.
  *
diff --git a/sys/net/gnrc/netapi/gnrc_netapi.c b/sys/net/gnrc/netapi/gnrc_netapi.c
index 5f80302524108256c0dedd64d6b57f1929482550..d110ebf34af4fd9e1ac0dd7f1141fe3aa3a97271 100644
--- a/sys/net/gnrc/netapi/gnrc_netapi.c
+++ b/sys/net/gnrc/netapi/gnrc_netapi.c
@@ -102,7 +102,7 @@ int gnrc_netapi_dispatch(gnrc_nettype_t type, uint32_t demux_ctx,
         gnrc_pktbuf_hold(pkt, numof - 1);
 
         while (sendto) {
-#ifdef MODULE_GNRC_NETAPI_MBOX
+#if defined(MODULE_GNRC_NETAPI_MBOX) || defined(MODULE_GNRC_NETAPI_CALLBACKS)
             int release = 0;
             switch (sendto->type) {
                 case GNRC_NETREG_TYPE_DEFAULT:
@@ -111,12 +111,19 @@ int gnrc_netapi_dispatch(gnrc_nettype_t type, uint32_t demux_ctx,
                         release = 1;
                     }
                     break;
+#ifdef MODULE_GNRC_NETAPI_MBOX
                 case GNRC_NETREG_TYPE_MBOX:
                     if (_snd_rcv_mbox(sendto->target.mbox, cmd, pkt) < 1) {
                         /* unable to dispatch packet */
                         release = 1;
                     }
                     break;
+#endif
+#ifdef MODULE_GNRC_NETAPI_CALLBACKS
+                case GNRC_NETREG_TYPE_CB:
+                    sendto->target.cbd->cb(cmd, pkt, sendto->target.cbd->ctx);
+                    break;
+#endif
                 default:
                     /* unknown dispatch type */
                     release = 1;