diff --git a/src/gatt-database.c b/src/gatt-database.c
index 25641da8a..dc75762f3 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -1302,7 +1302,7 @@ static void populate_devinfo_service(struct btd_gatt_database *database)
 static void register_core_services(struct btd_gatt_database *database)
 {
 	gatt_db_ccc_register(database->db, gatt_ccc_read_cb, gatt_ccc_write_cb,
-								database);
+							NULL, database);
 
 	populate_gap_service(database);
 	populate_gatt_service(database);
diff --git a/src/shared/gatt-db.c b/src/shared/gatt-db.c
index 12ff5badb..59d19eea3 100644
--- a/src/shared/gatt-db.c
+++ b/src/shared/gatt-db.c
@@ -43,10 +43,13 @@ static const bt_uuid_t included_service_uuid = { .type = BT_UUID16,
 					.value.u16 = GATT_INCLUDE_UUID };
 static const bt_uuid_t ext_desc_uuid = { .type = BT_UUID16,
 				.value.u16 = GATT_CHARAC_EXT_PROPER_UUID };
+static const bt_uuid_t ccc_uuid = { .type = BT_UUID16,
+				.value.u16 = GATT_CLIENT_CHARAC_CFG_UUID };
 
 struct gatt_db_ccc {
 	gatt_db_read_t read_func;
 	gatt_db_write_t write_func;
+	gatt_db_notify_t notify_func;
 	void *user_data;
 };
 
@@ -109,6 +112,7 @@ struct gatt_db_attribute {
 
 	gatt_db_read_t read_func;
 	gatt_db_write_t write_func;
+	gatt_db_notify_t notify_func;
 	void *user_data;
 
 	unsigned int read_id;
@@ -1047,12 +1051,20 @@ gatt_db_service_add_descriptor(struct gatt_db_attribute *attrib,
 					user_data);
 }
 
+static void find_ccc_value(struct gatt_db_attribute *attrib, void *user_data)
+{
+	uint16_t *handle = user_data;
+
+	gatt_db_attribute_get_char_data(attrib, NULL, handle, NULL, NULL, NULL);
+}
+
 struct gatt_db_attribute *
 gatt_db_service_add_ccc(struct gatt_db_attribute *attrib, uint32_t permissions)
 {
 	struct gatt_db *db;
 	struct gatt_db_attribute *ccc;
-	bt_uuid_t uuid;
+	struct gatt_db_attribute *value;
+	uint16_t handle = 0;
 
 	if (!attrib)
 		return NULL;
@@ -1062,9 +1074,17 @@ gatt_db_service_add_ccc(struct gatt_db_attribute *attrib, uint32_t permissions)
 	if (!db->ccc)
 		return NULL;
 
-	bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
+	/* Locate value handle */
+	gatt_db_service_foreach_char(attrib, find_ccc_value, &handle);
+
+	if (!handle)
+		return NULL;
+
+	value = gatt_db_get_attribute(db, handle);
+	if (!value || value->notify_func)
+		return NULL;
 
-	ccc = service_insert_descriptor(attrib->service, 0, &uuid,
+	ccc = service_insert_descriptor(attrib->service, 0, &ccc_uuid,
 					permissions,
 					db->ccc->read_func,
 					db->ccc->write_func,
@@ -1073,12 +1093,16 @@ gatt_db_service_add_ccc(struct gatt_db_attribute *attrib, uint32_t permissions)
 		return ccc;
 
 	gatt_db_attribute_set_fixed_length(ccc, 2);
+	ccc->notify_func = db->ccc->notify_func;
+	value->notify_func = db->ccc->notify_func;
 
 	return ccc;
 }
 
 void gatt_db_ccc_register(struct gatt_db *db, gatt_db_read_t read_func,
-				gatt_db_write_t write_func, void *user_data)
+				gatt_db_write_t write_func,
+				gatt_db_notify_t notify_func,
+				void *user_data)
 {
 	if (!db)
 		return;
@@ -1088,6 +1112,7 @@ void gatt_db_ccc_register(struct gatt_db *db, gatt_db_read_t read_func,
 
 	db->ccc->read_func = read_func;
 	db->ccc->write_func = write_func;
+	db->ccc->notify_func = notify_func;
 	db->ccc->user_data = user_data;
 }
 
@@ -2103,6 +2128,19 @@ bool gatt_db_attribute_write_result(struct gatt_db_attribute *attrib,
 	return true;
 }
 
+bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
+					struct gatt_db_attribute *ccc,
+					const uint8_t *value, size_t len,
+					struct bt_att *att)
+{
+	if (!attrib || !ccc || !attrib->notify_func)
+		return false;
+
+	attrib->notify_func(attrib, ccc, value, len, att, ccc->user_data);
+
+	return true;
+}
+
 bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib)
 {
 	if (!attrib)
diff --git a/src/shared/gatt-db.h b/src/shared/gatt-db.h
index 3de22403c..acd8f6a81 100644
--- a/src/shared/gatt-db.h
+++ b/src/shared/gatt-db.h
@@ -47,6 +47,10 @@ typedef void (*gatt_db_write_t) (struct gatt_db_attribute *attrib,
 					const uint8_t *value, size_t len,
 					uint8_t opcode, struct bt_att *att,
 					void *user_data);
+typedef void (*gatt_db_notify_t) (struct gatt_db_attribute *attrib,
+					struct gatt_db_attribute *ccc,
+					const uint8_t *value, size_t len,
+					struct bt_att *att, void *user_data);
 
 struct gatt_db_attribute *
 gatt_db_service_add_characteristic(struct gatt_db_attribute *attrib,
@@ -197,7 +201,9 @@ unsigned int gatt_db_register(struct gatt_db *db,
 bool gatt_db_unregister(struct gatt_db *db, unsigned int id);
 
 void gatt_db_ccc_register(struct gatt_db *db, gatt_db_read_t read_func,
-				gatt_db_write_t write_func, void *user_data);
+					gatt_db_write_t write_func,
+					gatt_db_notify_t notify_func,
+					void *user_data);
 
 typedef uint8_t (*gatt_db_authorize_cb_t)(struct gatt_db_attribute *attrib,
 					uint8_t opcode, struct bt_att *att,
@@ -275,6 +281,11 @@ bool gatt_db_attribute_write(struct gatt_db_attribute *attrib, uint16_t offset,
 bool gatt_db_attribute_write_result(struct gatt_db_attribute *attrib,
 						unsigned int id, int err);
 
+bool gatt_db_attribute_notify(struct gatt_db_attribute *attrib,
+					struct gatt_db_attribute *ccc,
+					const uint8_t *value, size_t len,
+					struct bt_att *att);
+
 bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib);
 
 void *gatt_db_attribute_get_user_data(struct gatt_db_attribute *attrib);
