diff --git a/epicardium/ble/ble_api.h b/epicardium/ble/ble_api.h
index 3c50745e91285c782ef6650dfa9c4645c360de64..059b0f6e85c964b6a4906743aed4698ee85427a3 100644
--- a/epicardium/ble/ble_api.h
+++ b/epicardium/ble/ble_api.h
@@ -27,9 +27,13 @@ void BleStart(void);
 /* ATT client module interface. Used by main BLE module */
 void bleValueUpdate(attEvt_t *pMsg);
 void bleDiscCback(dmConnId_t connId, uint8_t status);
-void ble_trigger_event(enum ble_event_type event);
+
 void ble_epic_att_api_init(void);
 void ble_epic_att_api_event(attEvt_t *att_event);
+void ble_epic_att_api_free_att_write_data(struct epic_att_write *w);
+
+void ble_epic_ble_api_trigger_event(enum epic_ble_event_type type, void *data);
+void ble_epic_ble_api_init(void);
 
 /**************************************************************************************************
   Data Types
diff --git a/epicardium/ble/ble_main.c b/epicardium/ble/ble_main.c
index 373f5f37e37bc4633a090c19b63e4335e500f262..61e43788d7a13569c906ed0024bfb3596b6d800b 100644
--- a/epicardium/ble/ble_main.c
+++ b/epicardium/ble/ble_main.c
@@ -38,7 +38,6 @@
 
 #include "ble_api.h"
 #include "epicardium.h"
-#include "api/interrupt-sender.h"
 #include "modules/log.h"
 #include "modules/config.h"
 
@@ -47,7 +46,6 @@
 static bool active;
 static uint8_t advertising_mode = APP_MODE_NONE;
 static uint8_t advertising_mode_target = APP_MODE_NONE;
-static enum ble_event_type ble_event;
 static struct epic_scan_report scan_reports[SCAN_REPORTS_NUM];
 static int scan_reports_head;
 static int scan_reports_tail;
@@ -620,27 +618,6 @@ void epic_ble_compare_response(bool confirmed)
 		/* error condition */
 	}
 }
-void ble_trigger_event(enum ble_event_type event)
-{
-	bool enabled;
-	epic_interrupt_is_enabled(EPIC_INT_BLE, &enabled);
-
-	/* Print a warning if the app is missing events. Missing scan results
-	 * is considered OK though, as they are queued and periodic. */
-	if(ble_event && enabled && ble_event != BLE_EVENT_SCAN_REPORT) {
-		LOG_WARN("ble", "Application missed event %u", ble_event);
-	}
-
-	ble_event = event;
-	api_interrupt_trigger(EPIC_INT_BLE);
-}
-
-enum ble_event_type epic_ble_get_event(void)
-{
-	enum ble_event_type event = ble_event;
-	ble_event = 0;
-	return event;
-}
 
 static void bleHandleNumericComparison(dmSecCnfIndEvt_t *pCnfInd)
 {
@@ -651,7 +628,7 @@ static void bleHandleNumericComparison(dmSecCnfIndEvt_t *pCnfInd)
 	pair_connId = (dmConnId_t)pCnfInd->hdr.param;
 	pair_confirm_value = DmSecGetCompareValue(pCnfInd->confirm);
 	LOG_INFO("ble", "Confirm Value: %ld", pair_confirm_value);
-	ble_trigger_event(BLE_EVENT_HANDLE_NUMERIC_COMPARISON);
+	ble_epic_ble_api_trigger_event(BLE_EVENT_HANDLE_NUMERIC_COMPARISON, NULL);
 }
 
 int epic_ble_get_scan_report(struct epic_scan_report *rpt)
@@ -672,7 +649,7 @@ static void scannerScanReport(dmEvt_t *pMsg)
 
 	int next_head = (scan_reports_head + 1) % SCAN_REPORTS_NUM;
 	if(next_head == scan_reports_tail) {
-		ble_trigger_event(BLE_EVENT_SCAN_REPORT);
+		ble_epic_ble_api_trigger_event(BLE_EVENT_SCAN_REPORT, NULL);
 		return;
 	}
 	scan_reports_head = next_head;
@@ -688,7 +665,7 @@ static void scannerScanReport(dmEvt_t *pMsg)
 
 	scan_report->directAddrType = pMsg->scanReport.directAddrType;
 	memcpy(scan_report->directAddr, pMsg->scanReport.directAddr, BDA_ADDR_LEN);
-	ble_trigger_event(BLE_EVENT_SCAN_REPORT);
+	ble_epic_ble_api_trigger_event(BLE_EVENT_SCAN_REPORT, NULL);
 
 	if((scan_reports_head + 1) % SCAN_REPORTS_NUM == scan_reports_tail) {
 		LOG_WARN("ble", "Application missing scan results");
@@ -807,7 +784,7 @@ static void bleProcMsg(bleMsg_t *pMsg)
       AppDbNvmStoreBond(last_pairing);
 
       pair_connId = DM_CONN_ID_NONE;
-      ble_trigger_event(BLE_EVENT_PAIRING_COMPLETE);
+      ble_epic_ble_api_trigger_event(BLE_EVENT_PAIRING_COMPLETE, NULL);
       /* After a successful pairing, bonding is disabled again.
        * We don't want that for now. */
       AppSetBondable(TRUE);
@@ -832,7 +809,7 @@ static void bleProcMsg(bleMsg_t *pMsg)
       DmSecGenerateEccKeyReq();
 
       pair_connId = DM_CONN_ID_NONE;
-      ble_trigger_event(BLE_EVENT_PAIRING_FAILED);
+      ble_epic_ble_api_trigger_event(BLE_EVENT_PAIRING_FAILED, NULL);
       break;
 
     case DM_SEC_ENCRYPT_IND:
@@ -980,7 +957,7 @@ void BleStart(void)
     hid_init();
   }
 
-  ble_epic_att_api_init();
+  ble_epic_ble_api_init();
 
   /* Reset the device */
   DmDevReset();
diff --git a/epicardium/ble/epic_att_api.c b/epicardium/ble/epic_att_api.c
index c155e80ee9a02585690438a65007ac549d8527bb..d35574200e3a54ce6700c57c220c25d8a47cb00a 100644
--- a/epicardium/ble/epic_att_api.c
+++ b/epicardium/ble/epic_att_api.c
@@ -14,52 +14,27 @@
 #include <stdio.h>
 #include <string.h>
 
-#define ATT_EVENT_QUEUE_SIZE 10
-#define ATT_WRITE_QUEUE_SIZE 10
-
-static QueueHandle_t att_event_queue;
-static uint8_t att_event_queue_buffer
-	[sizeof(struct epic_att_event) * ATT_EVENT_QUEUE_SIZE];
-static StaticQueue_t att_event_queue_data;
-
-static QueueHandle_t att_write_queue;
-static uint8_t att_write_queue_buffer
-	[sizeof(struct epic_att_write) * ATT_WRITE_QUEUE_SIZE];
-static StaticQueue_t att_write_queue_data;
-
-int epic_ble_get_att_event(struct epic_att_event *e)
-{
-	if (xQueueReceive(att_event_queue, e, 0) != pdTRUE) {
-		return -ENOENT;
-	}
-	return uxQueueMessagesWaiting(att_event_queue);
-}
-
 void ble_epic_att_api_event(attEvt_t *att_event)
 {
 	bool enabled;
 	epic_interrupt_is_enabled(EPIC_INT_BLE, &enabled);
 
 	if (enabled) {
-		if (xQueueSend(att_event_queue, att_event, 0) != pdTRUE) {
-			LOG_WARN("ble", "could not queue att event");
-		}
-		ble_trigger_event(BLE_EVENT_ATT_EVENT);
-	}
-}
+		attEvt_t *e = WsfBufAlloc(sizeof(*e));
 
-int epic_ble_get_att_write(struct epic_att_write *w)
-{
-	if (xQueueReceive(att_write_queue, w, 0) != pdTRUE) {
-		return -ENOENT;
+		if (e) {
+			memcpy(e, att_event, sizeof(*e));
+			ble_epic_ble_api_trigger_event(BLE_EVENT_ATT_EVENT, e);
+		} else {
+			LOG_WARN("ble", "could not allocate att event");
+		}
 	}
-	return uxQueueMessagesWaiting(att_event_queue);
 }
 
-int epic_ble_free_att_write(struct epic_att_write *w)
+void ble_epic_att_api_free_att_write_data(struct epic_att_write *w)
 {
 	WsfBufFree(w->buffer);
-	return 0;
+	WsfBufFree(w);
 }
 
 static uint8_t DynAttsWriteCback(
@@ -72,24 +47,28 @@ static uint8_t DynAttsWriteCback(
 	attsAttr_t *pAttr
 ) {
 	printf("DynAttsWriteCback %d, %d, %d\n", handle, len, offset);
-	struct epic_att_write att_write;
-	att_write.hdr.param = connId;
-	att_write.handle    = handle;
-	att_write.valueLen  = len;
-	att_write.operation = operation;
-	att_write.offset    = offset;
-
-	att_write.buffer = WsfBufAlloc(len);
 
-	if (att_write.buffer) {
-		memcpy(att_write.buffer, pValue, len);
-
-		if (xQueueSend(att_write_queue, &att_write, 0) != pdTRUE) {
-			LOG_WARN("ble", "could not queue att write");
-			epic_ble_free_att_write(&att_write);
+	struct epic_att_write *att_write = WsfBufAlloc(sizeof(*att_write));
+	;
+
+	if (att_write) {
+		att_write->hdr.param = connId;
+		att_write->handle    = handle;
+		att_write->valueLen  = len;
+		att_write->operation = operation;
+		att_write->offset    = offset;
+
+		att_write->buffer = WsfBufAlloc(len);
+
+		if (att_write->buffer) {
+			memcpy(att_write->buffer, pValue, len);
+			ble_epic_ble_api_trigger_event(
+				BLE_EVENT_ATT_WRITE, att_write
+			);
+		} else {
+			LOG_WARN("ble", "could allocate att write data");
+			WsfBufFree(att_write);
 		}
-
-		ble_trigger_event(BLE_EVENT_ATT_WRITE);
 	} else {
 		LOG_WARN("ble", "could allocate att write");
 	}
@@ -144,18 +123,6 @@ int epic_ble_atts_dyn_delete_groups(void)
 
 void ble_epic_att_api_init(void)
 {
-	att_event_queue = xQueueCreateStatic(
-		ATT_EVENT_QUEUE_SIZE,
-		sizeof(struct epic_att_event),
-		att_event_queue_buffer,
-		&att_event_queue_data
-	);
-	att_write_queue = xQueueCreateStatic(
-		ATT_WRITE_QUEUE_SIZE,
-		sizeof(struct epic_att_write),
-		att_write_queue_buffer,
-		&att_write_queue_data
-	);
 }
 
 int epic_ble_atts_handle_value_ntf(
diff --git a/epicardium/ble/epic_ble_api.c b/epicardium/ble/epic_ble_api.c
new file mode 100644
index 0000000000000000000000000000000000000000..4e68170be752fea04025edbdd222720a0d3a41df
--- /dev/null
+++ b/epicardium/ble/epic_ble_api.c
@@ -0,0 +1,71 @@
+#include "ble_api.h"
+
+#include "epicardium.h"
+#include "modules/log.h"
+#include "api/interrupt-sender.h"
+
+#include "wsf_buf.h"
+
+#include "FreeRTOS.h"
+#include "queue.h"
+
+#include <stdint.h>
+
+#define BLE_EVENT_QUEUE_SIZE 10
+
+static QueueHandle_t ble_event_queue;
+static uint8_t ble_event_queue_buffer
+	[sizeof(struct epic_ble_event) * BLE_EVENT_QUEUE_SIZE];
+static StaticQueue_t ble_event_queue_data;
+
+int epic_ble_free_event(struct epic_ble_event *e)
+{
+	if (e->data) {
+		if (e->type == BLE_EVENT_ATT_WRITE) {
+			ble_epic_att_api_free_att_write_data(e->att_write);
+		} else {
+			// Generic free
+			WsfBufFree(e->data);
+		}
+	}
+	return 0;
+}
+
+void ble_epic_ble_api_trigger_event(enum epic_ble_event_type type, void *data)
+{
+	bool enabled;
+	epic_interrupt_is_enabled(EPIC_INT_BLE, &enabled);
+
+	struct epic_ble_event e = { .type = type, .data = data };
+
+	if (xQueueSend(ble_event_queue, &e, 0) != pdTRUE) {
+		/* Print a warning if the app is missing events. Missing scan results
+		 * is considered OK though, as they are queued and periodic. */
+		if (type != BLE_EVENT_SCAN_REPORT) {
+			LOG_WARN("ble", "Application missed event %u", type);
+		}
+		epic_ble_free_event(&e);
+	}
+
+	api_interrupt_trigger(EPIC_INT_BLE);
+}
+
+int epic_ble_get_event(struct epic_ble_event *e)
+{
+	if (xQueueReceive(ble_event_queue, e, 0) != pdTRUE) {
+		return -ENOENT;
+	}
+	return uxQueueMessagesWaiting(ble_event_queue);
+}
+
+void ble_epic_ble_api_init(void)
+{
+	ble_event_queue = xQueueCreateStatic(
+		BLE_EVENT_QUEUE_SIZE,
+		sizeof(struct epic_ble_event),
+		ble_event_queue_buffer,
+		&ble_event_queue_data
+	);
+
+	ble_epic_att_api_init();
+}
diff --git a/epicardium/ble/meson.build b/epicardium/ble/meson.build
index 66a4566ad2c78e1fb99b317a9d6d27830f26ff06..2372440b4e5416aeb6fa57c52154afce12608b7d 100644
--- a/epicardium/ble/meson.build
+++ b/epicardium/ble/meson.build
@@ -1,5 +1,6 @@
 ble_sources = files(
   'ble.c',
+  'epic_ble_api.c',
   'epic_att_api.c',
   'stack.c',
   'ble_main.c',
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index ec74d98132d7aae9394d6ec9ecadcc55f3c222c9..6ac9a8d6121ca5e89f750f3e1a7f2e475e0ba9ee 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -171,7 +171,7 @@ typedef _Bool bool;
 #define API_BLE_ATTS_HANDLE_VALUE_NTF         0x166
 #define API_BLE_ATTS_HANDLE_VALUE_IND         0x167
 #define API_BLE_ATTS_GET_ATTR                 0x168
-#define API_BLE_ATT_GET_EVENT                 0x169
+#define API_BLE_FREE_EVENT                 0x169
 #define API_BLE_ATTS_SEND_SERVICE_CHANGED_IND 0x16A
 #define API_BLE_ATTS_DYN_DELETE_GROUPS        0x16B
 #define API_BLE_ATTS_SET_BUFFER               0x1BC
@@ -2335,7 +2335,7 @@ API(API_CONFIG_SET_STRING, int epic_config_set_string(const char *key, const cha
 /**
  * BLE event type
  */
-enum ble_event_type {
+enum epic_ble_event_type {
 	/** No event pending */
 	BLE_EVENT_NONE                                    = 0,
 	/** Numeric comparison requested */
@@ -2348,8 +2348,45 @@ enum ble_event_type {
 	BLE_EVENT_SCAN_REPORT                             = 4,
 	BLE_EVENT_ATT_EVENT                               = 5,
 	BLE_EVENT_ATT_WRITE                               = 6,
+	BLE_EVENT_DM_EVENT                                = 6,
 };
 
+struct epic_wsf_header
+{
+  uint16_t        param;          /*!< \brief General purpose parameter passed to event handler */
+  uint8_t         event;          /*!< \brief General purpose event value passed to event handler */
+  uint8_t         status;         /*!< \brief General purpose status value passed to event handler */
+};
+
+struct epic_att_event
+{
+  struct epic_wsf_header hdr;          /*!< \brief Header structure */
+  uint8_t               *pValue;      /*!< \brief Value */
+  uint16_t              valueLen;     /*!< \brief Value length */
+  uint16_t              handle;       /*!< \brief Attribute handle */
+  uint8_t                continuing;   /*!< \brief TRUE if more response packets expected */
+  uint16_t              mtu;          /*!< \brief Negotiated MTU value */
+};
+
+struct epic_att_write
+{
+  struct epic_wsf_header hdr;          /*!< \brief Header structure */
+  uint16_t              valueLen;     /*!< \brief Value length */
+  uint16_t              handle;       /*!< \brief Attribute handle */
+  uint8_t operation;
+  uint16_t offset;
+  void *buffer;
+};
+
+
+struct epic_ble_event {
+	enum epic_ble_event_type type;
+	union {
+		void *data;
+		struct epic_att_event *att_event;
+		struct epic_att_write *att_write;
+	};
+};
 
 /**
  * Scan report data. Bases on ``hciLeAdvReportEvt_t`` from BLE stack.
@@ -2397,15 +2434,10 @@ API_ISR(EPIC_INT_BLE, epic_isr_ble);
 /**
  * Retreive the event which triggered :c:func:`epic_isr_ble`
  *
- * The handling code needs to ensure to handle interrupts in a timely
- * manner as new events will overwrite each other. Reading the event
- * automatically resets it to :c:data:`BLE_EVENT_NONE`.
- *
- * :return: Event which triggered the interrupt.
- *
  * .. versionadded:: 1.16
+ * .. versionchanged:: 1.17
  */
-API(API_BLE_GET_EVENT, enum ble_event_type epic_ble_get_event(void));
+API(API_BLE_GET_EVENT, int epic_ble_get_event(struct epic_ble_event *e));
 
 /**
  * Retrieve the compare value of an ongoing pairing procedure.
@@ -2532,37 +2564,7 @@ API(API_BLE_ATTS_HANDLE_VALUE_NTF, int epic_ble_atts_handle_value_ntf(uint8_t co
 API(API_BLE_ATTS_HANDLE_VALUE_IND, int epic_ble_atts_handle_value_ind(uint8_t connId, uint16_t handle, uint16_t valueLen, uint8_t *pValue));
 API(API_BLE_ATTS_SET_BUFFER, int epic_ble_atts_set_buffer(uint16_t value_handle, size_t len, bool append));
 
-struct epic_wsf_header
-{
-  uint16_t        param;          /*!< \brief General purpose parameter passed to event handler */
-  uint8_t         event;          /*!< \brief General purpose event value passed to event handler */
-  uint8_t         status;         /*!< \brief General purpose status value passed to event handler */
-};
-
-struct epic_att_event
-{
-  struct epic_wsf_header hdr;          /*!< \brief Header structure */
-  uint8_t               *pValue;      /*!< \brief Value */
-  uint16_t              valueLen;     /*!< \brief Value length */
-  uint16_t              handle;       /*!< \brief Attribute handle */
-  uint8_t                continuing;   /*!< \brief TRUE if more response packets expected */
-  uint16_t              mtu;          /*!< \brief Negotiated MTU value */
-};
-
-API(API_BLE_ATT_GET_EVENT, int epic_ble_get_att_event(struct epic_att_event *e));
-
-struct epic_att_write
-{
-  struct epic_wsf_header hdr;          /*!< \brief Header structure */
-  uint16_t              valueLen;     /*!< \brief Value length */
-  uint16_t              handle;       /*!< \brief Attribute handle */
-  uint8_t operation;
-  uint16_t offset;
-  void *buffer;
-};
-
-API(API_BLE_GET_ATT_WRITE, int epic_ble_get_att_write(struct epic_att_write *w));
-API(API_BLE_FREE_ATT_WRITE, int epic_ble_free_att_write(struct epic_att_write *w));
+API(API_BLE_FREE_EVENT, int epic_ble_free_event(struct epic_ble_event *e));
 
 
 #endif /* _EPICARDIUM_H */
diff --git a/pycardium/modules/modbluetooth_card10.c b/pycardium/modules/modbluetooth_card10.c
index 5395a68150d9bdf397727e493ea2afc9e5765055..36dd81dcb230ad0307e188e2b866dd3bffa2b46d 100644
--- a/pycardium/modules/modbluetooth_card10.c
+++ b/pycardium/modules/modbluetooth_card10.c
@@ -145,13 +145,15 @@ static void raise(void)
 
 static void clear_events(void)
 {
-	struct epic_att_event att_event;
-	struct epic_att_write att_write;
-	epic_ble_get_event();
-	while (epic_ble_get_att_event(&att_event) >= 0)
-		;
-	while (epic_ble_get_att_write(&att_write) >= 0)
-		epic_ble_free_att_write(&att_write);
+	struct epic_ble_event ble_event;
+	int ret;
+
+	do {
+		ret = epic_ble_get_event(&ble_event);
+		if (ret >= 0) {
+			epic_ble_free_event(&ble_event);
+		}
+	} while (ret >= 0);
 }
 
 static void handle_att_event(struct epic_att_event *att_event)
@@ -217,30 +219,21 @@ static void handle_att_write(struct epic_att_write *att_write)
 
 static mp_obj_t mp_ble_poll_events(mp_obj_t interrupt_id)
 {
-	enum ble_event_type event = epic_ble_get_event();
-
-	if (event == BLE_EVENT_ATT_EVENT) {
-		struct epic_att_event att_event;
-		int ret;
-		do {
-			ret = epic_ble_get_att_event(&att_event);
-			if (ret >= 0) {
-				handle_att_event(&att_event);
+	struct epic_ble_event ble_event;
+	int ret;
+
+	do {
+		ret = epic_ble_get_event(&ble_event);
+		if (ret >= 0) {
+			if (ble_event.type == BLE_EVENT_ATT_EVENT) {
+				handle_att_event(ble_event.att_event);
 			}
-		} while (ret >= 0);
-	}
-
-	if (event == BLE_EVENT_ATT_WRITE) {
-		struct epic_att_write att_write;
-		int ret;
-		do {
-			ret = epic_ble_get_att_write(&att_write);
-			if (ret >= 0) {
-				handle_att_write(&att_write);
-				epic_ble_free_att_write(&att_write);
+			if (ble_event.type == BLE_EVENT_ATT_WRITE) {
+				handle_att_write(ble_event.att_write);
 			}
-		} while (ret >= 0);
-	}
+			epic_ble_free_event(&ble_event);
+		}
+	} while (ret >= 0);
 
 	return mp_const_none;
 }
diff --git a/pycardium/modules/sys_ble.c b/pycardium/modules/sys_ble.c
index 7b95d8d4d9081a72ed687eeb0c40fd750589e09e..647b3baa9cf09f3996032df7fb5e99c9ec5589ac 100644
--- a/pycardium/modules/sys_ble.c
+++ b/pycardium/modules/sys_ble.c
@@ -84,7 +84,11 @@ static MP_DEFINE_CONST_FUN_OBJ_0(
 
 static mp_obj_t mp_ble_get_event(void)
 {
-	return mp_obj_new_int(epic_ble_get_event());
+	struct epic_ble_event e;
+	if (epic_ble_get_event(&e) >= 0) {
+		return mp_obj_new_int(e.type);
+	}
+	return BLE_EVENT_NONE;
 }
 static MP_DEFINE_CONST_FUN_OBJ_0(ble_get_event_obj, mp_ble_get_event);