Index: b/include/asterisk/cel.h
===================================================================
--- a/include/asterisk/cel.h
+++ b/include/asterisk/cel.h
@@ -249,6 +249,7 @@ struct ast_cel_general_config {
 		AST_STRING_FIELD(date_format); /*!< The desired date format for logging */
 	);
 	int enable;			/*!< Whether CEL is enabled */
+	int nova_compatibility;
 	int64_t events;			/*!< The events to be logged */
 	/*! The apps for which to log app start and end events. This is
 	 * ast_str_container_alloc()ed and filled with ao2-allocated
Index: b/include/asterisk/nova.h
===================================================================
--- /dev/null
+++ b/include/asterisk/nova.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 Avencall
+ * SPDX-License-Identifier: GPL-3.0+
+ */
+
+#ifndef _ASTERISK_NOVA_H_
+#define _ASTERISK_NOVA_H_
+
+struct ast_channel_snapshot;
+struct ast_multi_channel_blob;
+
+void ast_nova_compatibility_cleanup(void);
+
+void ast_nova_compatibility_set_enabled(int enabled);
+
+void ast_nova_compatibility_add_channel(struct ast_channel_snapshot *snapshot);
+
+void ast_nova_compatibility_remove_channel(struct ast_channel_snapshot *snapshot);
+
+void ast_nova_compatibility_on_channel_answer(struct ast_channel_snapshot *snapshot);
+
+void ast_nova_compatibility_on_channel_xivo_outcall(struct ast_channel_snapshot *snapshot);
+
+void ast_nova_compatibility_on_channel_xivo_incall(struct ast_channel_snapshot *snapshot);
+
+void ast_nova_compatibility_on_dial(struct ast_multi_channel_blob *blob);
+
+void ast_nova_compatibility_on_local_bridge(struct ast_multi_channel_blob *blob);
+
+void ast_nova_compatibility_augment_bridge_enter_event(struct ast_json *extra, struct ast_bridge_snapshot *snapshot);
+
+void ast_nova_compatibility_augment_attended_transfer_event(struct ast_json *extra,
+	struct ast_channel_snapshot *channel2, struct ast_channel_snapshot *transferee, struct ast_channel_snapshot *transfer_target);
+
+#endif /* _ASTERISK_NOVA_H_ */
Index: b/main/cel.c
===================================================================
--- a/main/cel.c
+++ b/main/cel.c
@@ -58,6 +58,7 @@
 #include "asterisk/pickup.h"
 #include "asterisk/core_local.h"
 #include "asterisk/taskprocessor.h"
+#include "asterisk/nova.h"
 
 /*** DOCUMENTATION
 	<configInfo name="cel" language="en_US">
@@ -73,6 +74,9 @@
 					</since>
 					<synopsis>Determines whether CEL is enabled</synopsis>
 				</configOption>
+				<configOption name="nova_compatibility">
+					<synopsis>Determines whether CEL NOVA compatibility is enabled</synopsis>
+				</configOption>
 				<configOption name="dateformat">
 					<since>
 						<version>12.0.0</version>
@@ -375,6 +379,16 @@ unsigned int ast_cel_check_enabled(void)
 	return enabled;
 }
 
+static int nova_compatibility_check_enabled(void)
+{
+	struct cel_config *cfg = ao2_global_obj_ref(cel_configs);
+	int enabled;
+
+	enabled = (!cfg || !cfg->general) ? 0 : cfg->general->nova_compatibility;
+	ao2_cleanup(cfg);
+	return enabled;
+}
+
 static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
 	unsigned int i;
@@ -950,7 +964,12 @@ static void cel_channel_state_change(
 {
 	int is_hungup, was_hungup;
 
+ 	if (!new_snapshot) {
+		ast_nova_compatibility_remove_channel(old_snapshot);
+	}
+
 	if (!old_snapshot) {
+		ast_nova_compatibility_add_channel(new_snapshot);
 		cel_report_event(new_snapshot, AST_CEL_CHANNEL_START, event_time, NULL, NULL, NULL);
 		return;
 	}
@@ -978,6 +997,7 @@ static void cel_channel_state_change(
 	}
 
 	if (old_snapshot->state != new_snapshot->state && new_snapshot->state == AST_STATE_UP) {
+		ast_nova_compatibility_on_channel_answer(new_snapshot);
 		cel_report_event(new_snapshot, AST_CEL_ANSWER, event_time, NULL, NULL, NULL);
 		return;
 	}
@@ -1115,6 +1135,8 @@ static void cel_bridge_enter_cb(
 		return;
 	}
 
+	ast_nova_compatibility_augment_bridge_enter_event(extra, snapshot);
+
 	peer_str = cel_generate_peer_str(snapshot, chan_snapshot);
 	if (!peer_str) {
 		return;
@@ -1287,6 +1309,8 @@ static void cel_dial_cb(void *data, stru
 		return;
 	}
 
+	ast_nova_compatibility_on_dial(blob);
+
 	if (!ast_strlen_zero(get_blob_variable(blob, "forward"))) {
 		struct ast_json *extra;
 
@@ -1320,6 +1344,12 @@ static void cel_generic_cb(
 			struct ast_json *extra = ast_json_object_get(event_details, "extra");
 			cel_report_event(obj->snapshot, event_type, stasis_message_timestamp(message),
 				event, extra, NULL);
+
+			if (strcmp(event, "XIVO_OUTCALL") == 0) {
+				ast_nova_compatibility_on_channel_xivo_outcall(obj->snapshot);
+			} else if (strcmp(event, "XIVO_INCALL") == 0) {
+				ast_nova_compatibility_on_channel_xivo_incall(obj->snapshot);
+			}
 			break;
 		}
 	case AST_CEL_STREAM_END:
@@ -1388,6 +1418,7 @@ static void cel_attended_transfer_cb(
 		channel2 = xfer->to_transfer_target.channel_snapshot;
 	}
 
+
 	switch (xfer->dest_type) {
 	case AST_ATTENDED_TRANSFER_DEST_FAIL:
 		return;
@@ -1424,6 +1455,7 @@ static void cel_attended_transfer_cb(
 		}
 		break;
 	}
+	ast_nova_compatibility_augment_attended_transfer_event(extra, channel2, xfer->transferee, xfer->target);
 	cel_report_event(channel1, AST_CEL_ATTENDEDTRANSFER, stasis_message_timestamp(message),
 		NULL, extra, NULL);
 	ast_json_unref(extra);
@@ -1496,6 +1528,15 @@ static void cel_local_optimization_begin
 	cel_local_optimization_cb_helper(data, sub, message, AST_CEL_LOCAL_OPTIMIZE_BEGIN);
 }
 
+static void cel_local_bridge_cb(
+	void *data, struct stasis_subscription *sub,
+	struct stasis_message *message)
+{
+	struct ast_multi_channel_blob *blob = stasis_message_data(message);
+
+	ast_nova_compatibility_on_local_bridge(blob);
+}
+
 static void destroy_routes(void)
 {
 	stasis_message_router_unsubscribe_and_join(cel_state_router);
@@ -1527,6 +1568,7 @@ static int unload_module(void)
 	ao2_global_obj_release(cel_dialstatus_store);
 	ao2_global_obj_release(cel_linkedids);
 	ao2_global_obj_release(cel_backends);
+	ast_nova_compatibility_cleanup();
 
 	return 0;
 }
@@ -1646,6 +1688,11 @@ static int create_routes(void)
 		cel_local_optimization_begin_cb,
 		NULL);
 
+	ret |= stasis_message_router_add(cel_state_router,
+		ast_local_bridge_type(),
+		cel_local_bridge_cb,
+		NULL);
+
 	if (ret) {
 		ast_log(AST_LOG_ERROR, "Failed to register for Stasis messages\n");
 	}
@@ -1697,6 +1744,7 @@ static int load_module(void)
 	}
 
 	aco_option_register(&cel_cfg_info, "enable", ACO_EXACT, general_options, "no", OPT_BOOL_T, 1, FLDSET(struct ast_cel_general_config, enable));
+	aco_option_register(&cel_cfg_info, "nova_compatibility", ACO_EXACT, general_options, "no", OPT_BOOL_T, 1, FLDSET(struct ast_cel_general_config, nova_compatibility));
 	aco_option_register(&cel_cfg_info, "dateformat", ACO_EXACT, general_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_cel_general_config, date_format));
 	aco_option_register_custom(&cel_cfg_info, "apps", ACO_EXACT, general_options, "", apps_handler, 0);
 	aco_option_register_custom(&cel_cfg_info, "events", ACO_EXACT, general_options, "", events_handler, 0);
@@ -1716,6 +1764,8 @@ static int load_module(void)
 		ao2_ref(cel_cfg, -1);
 	}
 
+	ast_nova_compatibility_set_enabled(nova_compatibility_check_enabled());
+
 	if (create_subscriptions()) {
 		return AST_MODULE_LOAD_FAILURE;
 	}
@@ -1748,6 +1798,8 @@ static int reload_module(void)
 
 	ast_verb(3, "CEL logging %sabled.\n", is_enabled ? "en" : "dis");
 
+	ast_nova_compatibility_set_enabled(nova_compatibility_check_enabled());
+
 	return 0;
 }
 
Index: b/main/nova.c
===================================================================
--- /dev/null
+++ b/main/nova.c
@@ -0,0 +1,540 @@
+/*
+ * Copyright 2016 Avencall
+ * SPDX-License-Identifier: GPL-3.0+
+ */
+
+#include "asterisk.h"
+
+#include "asterisk/astobj2.h"
+#include "asterisk/channel.h"
+#include "asterisk/json.h"
+#include "asterisk/lock.h"
+#include "asterisk/nova.h"
+#include "asterisk/stasis_bridges.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/strings.h"
+
+#define MAX_NOVA_NAME 40
+
+#define NOVA_NAME_UNKNOWN "unk:"
+
+static AO2_GLOBAL_OBJ_STATIC(nova_channel_infos);
+
+enum nova_channel_type {
+	NOVA_CHANNEL_TYPE_UNKNOWN,
+	NOVA_CHANNEL_TYPE_INTERNAL,
+	NOVA_CHANNEL_TYPE_INCALL,
+	NOVA_CHANNEL_TYPE_OUTCALL,
+};
+
+struct nova_channel_info {
+	struct nova_channel_info *local_channel_peer;
+	struct nova_channel_info *local_channel_relation;
+	char uniqueid[AST_MAX_UNIQUEID];
+	char nova_name[MAX_NOVA_NAME];
+	enum nova_channel_type type;
+	int xivo_outcall_event;
+	int is_local_channel;
+};
+
+static int nova_channel_infos_hash(const void *obj, const int flags)
+{
+	const struct nova_channel_info *info;
+	const char *key;
+
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_KEY:
+		key = obj;
+		break;
+	case OBJ_SEARCH_OBJECT:
+		info = obj;
+		key = info->uniqueid;
+		break;
+	default:
+		/* Hash can only work on something with a full key. */
+		ast_assert(0);
+		return 0;
+	}
+	return ast_str_hash(key);
+}
+
+static int nova_channel_infos_cmp(void *obj, void *arg, int flags)
+{
+	const struct nova_channel_info *object_left = obj;
+	const struct nova_channel_info *object_right = arg;
+	const char *right_key = arg;
+	int cmp;
+
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_OBJECT:
+		right_key = object_right->uniqueid;
+		/* Fall through */
+	case OBJ_SEARCH_KEY:
+		cmp = strcmp(object_left->uniqueid, right_key);
+		break;
+	case OBJ_SEARCH_PARTIAL_KEY:
+		cmp = strncmp(object_left->uniqueid, right_key, strlen(right_key));
+		break;
+	default:
+		/*
+		 * What arg points to is specific to this traversal callback
+		 * and has no special meaning to astobj2.
+		 */
+		cmp = 0;
+		break;
+	}
+	if (cmp) {
+		return 0;
+	}
+	/*
+	 * At this point the traversal callback is identical to a sorted
+	 * container.
+	 */
+	return CMP_MATCH;
+}
+
+static struct nova_channel_info *nova_channel_info_alloc(const char *uniqueid)
+{
+	struct nova_channel_info *info;
+
+	info = ao2_alloc(sizeof(*info), NULL);
+	if (!info) {
+		return NULL;
+	}
+
+	ast_copy_string(info->uniqueid, uniqueid, sizeof(info->uniqueid));
+	info->type = NOVA_CHANNEL_TYPE_UNKNOWN;
+
+	return info;
+}
+
+static void nova_channel_info_set_local_channel_peer(struct nova_channel_info *info, struct nova_channel_info *peer_info)
+{
+	ao2_replace(info->local_channel_peer, peer_info);
+}
+
+static void nova_channel_info_set_local_channel_relation(struct nova_channel_info *info, struct nova_channel_info *relation_info)
+{
+	ao2_replace(info->local_channel_relation, relation_info);
+}
+
+static struct ast_json *nova_channel_info_get_nova_name(struct nova_channel_info *info)
+{
+	char buf[MAX_NOVA_NAME + 4];
+	const char *prefix;
+	struct ast_json *json;
+
+	switch (info->type) {
+	case NOVA_CHANNEL_TYPE_INTERNAL:
+		prefix = "int";
+		break;
+	case NOVA_CHANNEL_TYPE_INCALL:
+		prefix = "inc";
+		break;
+	case NOVA_CHANNEL_TYPE_OUTCALL:
+		prefix = "out";
+		break;
+	default:
+		prefix = NULL;
+		break;
+	}
+
+	if (info->is_local_channel || !prefix) {
+		json = ast_json_string_create(NOVA_NAME_UNKNOWN);
+	} else {
+		snprintf(buf, sizeof(buf), "%s:%s", prefix, info->nova_name);
+		json = ast_json_string_create(buf);
+	}
+
+	return json;
+}
+
+/*
+ * info MUST NOT be locked
+ */
+static struct ast_json *nova_channel_info_get_effective_nova_name(struct nova_channel_info *info)
+{
+	struct nova_channel_info *peer_info = NULL;
+	struct nova_channel_info *relation_info = NULL;
+	struct ast_json *json = NULL;
+
+	ao2_lock(info);
+	if (!info->is_local_channel) {
+		json = nova_channel_info_get_nova_name(info);
+	} else {
+		peer_info = info->local_channel_peer;
+		if (peer_info) {
+			ao2_ref(peer_info, +1);
+		}
+	}
+	ao2_unlock(info);
+
+	if (json || !peer_info) {
+		goto end;
+	}
+
+	ao2_lock(peer_info);
+	relation_info = peer_info->local_channel_relation;
+	if (relation_info) {
+		ao2_ref(relation_info, +1);
+	}
+	ao2_unlock(peer_info);
+
+	if (!relation_info) {
+		json = ast_json_string_create(NOVA_NAME_UNKNOWN);
+	} else {
+		ao2_lock(relation_info);
+		json = nova_channel_info_get_nova_name(relation_info);
+		ao2_unlock(relation_info);
+	}
+
+end:
+	ao2_cleanup(peer_info);
+	ao2_cleanup(relation_info);
+
+	if (!json) {
+		json = ast_json_null();
+	}
+
+	return json;
+}
+
+static struct nova_channel_info *get_channel_info(struct ao2_container *infos, const char *uniqueid)
+{
+	return ao2_find(infos, uniqueid, OBJ_SEARCH_KEY);
+}
+
+static struct ast_json *get_nova_name_from_uniqueid(struct ao2_container *infos, const char *uniqueid)
+{
+	struct nova_channel_info *info;
+	struct ast_json *json;
+
+	info = get_channel_info(infos, uniqueid);
+	if (!info) {
+		return ast_json_null();
+	}
+
+	json = nova_channel_info_get_effective_nova_name(info);
+	ao2_ref(info, -1);
+
+	return json;
+}
+
+static struct ast_json *get_nova_name_from_snapshot(struct ao2_container *infos, struct ast_channel_snapshot *snapshot)
+{
+	if (!snapshot) {
+		return ast_json_null();
+	}
+
+	return get_nova_name_from_uniqueid(infos, snapshot->base->uniqueid);
+}
+
+void ast_nova_compatibility_add_channel(struct ast_channel_snapshot *snapshot)
+{
+	struct ao2_container *infos = ao2_global_obj_ref(nova_channel_infos);
+	struct nova_channel_info *info = NULL;
+
+	if (!infos) {
+		return;
+	}
+
+	info = nova_channel_info_alloc(snapshot->base->uniqueid);
+	if (!info) {
+		goto end;
+	}
+
+	if (strcmp(snapshot->base->type, "Local") == 0) {
+		info->is_local_channel = 1;
+	} else if (!ast_strlen_zero(snapshot->base->accountcode)) {
+		ast_copy_string(info->nova_name, snapshot->base->accountcode, sizeof(info->nova_name));
+		info->type = NOVA_CHANNEL_TYPE_INTERNAL;
+	}
+
+	ao2_link(infos, info);
+
+end:
+	ao2_ref(infos, -1);
+	ao2_cleanup(info);
+}
+
+void ast_nova_compatibility_remove_channel(struct ast_channel_snapshot *snapshot)
+{
+	struct ao2_container *infos = ao2_global_obj_ref(nova_channel_infos);
+	struct nova_channel_info *info = NULL;
+
+	if (!infos) {
+		return;
+	}
+
+	info = ao2_find(infos, snapshot->base->uniqueid, OBJ_SEARCH_KEY | OBJ_UNLINK);
+	if (!info) {
+		goto end;
+	}
+
+	/* destroy nova channel info */
+	ao2_lock(info);
+	if (info->local_channel_peer) {
+		ao2_ref(info->local_channel_peer, -1);
+		info->local_channel_peer = NULL;
+	}
+
+	if (info->local_channel_relation) {
+		ao2_ref(info->local_channel_relation, -1);
+		info->local_channel_relation = NULL;
+	}
+	ao2_unlock(info);
+
+end:
+	ao2_ref(infos, -1);
+	ao2_cleanup(info);
+}
+
+void ast_nova_compatibility_on_channel_answer(struct ast_channel_snapshot *snapshot)
+{
+	struct ao2_container *infos = ao2_global_obj_ref(nova_channel_infos);
+	struct nova_channel_info *info = NULL;
+
+	if (!infos) {
+		return;
+	}
+
+	info = get_channel_info(infos, snapshot->base->uniqueid);
+	if (!info) {
+		goto end;
+	}
+
+	ao2_lock(info);
+	if (info->type == NOVA_CHANNEL_TYPE_OUTCALL) {
+		ast_copy_string(info->nova_name, snapshot->caller->number, sizeof(info->nova_name));
+	}
+	ao2_unlock(info);
+
+end:
+	ao2_ref(infos, -1);
+	ao2_cleanup(info);
+}
+
+void ast_nova_compatibility_on_channel_xivo_outcall(struct ast_channel_snapshot *snapshot)
+{
+	struct ao2_container *infos = ao2_global_obj_ref(nova_channel_infos);
+	struct nova_channel_info *info = NULL;
+
+	if (!infos) {
+		return;
+	}
+
+	info = get_channel_info(infos, snapshot->base->uniqueid);
+	if (!info) {
+		goto end;
+	}
+
+	ao2_lock(info);
+	info->xivo_outcall_event = 1;
+	ao2_unlock(info);
+
+end:
+	ao2_ref(infos, -1);
+	ao2_cleanup(info);
+}
+
+void ast_nova_compatibility_on_channel_xivo_incall(struct ast_channel_snapshot *snapshot)
+{
+	struct ao2_container *infos = ao2_global_obj_ref(nova_channel_infos);
+	struct nova_channel_info *info = NULL;
+
+	if (!infos) {
+		return;
+	}
+
+	info = get_channel_info(infos, snapshot->base->uniqueid);
+	if (!info) {
+		goto end;
+	}
+
+	ao2_lock(info);
+	ast_copy_string(info->nova_name, snapshot->caller->number, sizeof(info->nova_name));
+	info->type = NOVA_CHANNEL_TYPE_INCALL;
+	ao2_unlock(info);
+
+end:
+	ao2_ref(infos, -1);
+	ao2_cleanup(info);
+}
+
+static const char *get_blob_variable(struct ast_multi_channel_blob *blob, const char *varname)
+{
+	struct ast_json *json = ast_multi_channel_blob_get_json(blob);
+	if (!json) {
+		return NULL;
+	}
+
+	json = ast_json_object_get(json, varname);
+	if (!json) {
+		return NULL;
+	}
+
+	return ast_json_string_get(json);
+}
+
+void ast_nova_compatibility_on_dial(struct ast_multi_channel_blob *blob)
+{
+	struct ao2_container *infos = ao2_global_obj_ref(nova_channel_infos);
+	struct ast_channel_snapshot *caller;
+	struct ast_channel_snapshot *peer;
+	struct nova_channel_info *caller_info = NULL;
+	struct nova_channel_info *peer_info = NULL;
+	const char *dialstatus;
+	int caller_xivo_outcall_event;
+
+	if (!infos) {
+		return;
+	}
+
+	dialstatus = get_blob_variable(blob, "dialstatus");
+	if (!dialstatus || !ast_strlen_zero(dialstatus)) {
+		goto end;
+	}
+
+	caller = ast_multi_channel_blob_get_channel(blob, "caller");
+	peer = ast_multi_channel_blob_get_channel(blob, "peer");
+	if (!caller || !peer) {
+		goto end;
+	}
+
+	caller_info = get_channel_info(infos, caller->base->uniqueid);
+	peer_info = get_channel_info(infos, peer->base->uniqueid);
+	if (!caller_info || !peer_info) {
+		goto end;
+	}
+
+	ao2_lock(caller_info);
+	if (caller_info->is_local_channel) {
+		nova_channel_info_set_local_channel_relation(caller_info, peer_info);
+	}
+	caller_xivo_outcall_event = caller_info->xivo_outcall_event;
+	ao2_unlock(caller_info);
+
+	ao2_lock(peer_info);
+	if (peer_info->is_local_channel) {
+		nova_channel_info_set_local_channel_relation(peer_info, caller_info);
+	} else if (peer_info->type == NOVA_CHANNEL_TYPE_UNKNOWN && caller_xivo_outcall_event) {
+		peer_info->type = NOVA_CHANNEL_TYPE_OUTCALL;
+	}
+	ao2_unlock(peer_info);
+
+end:
+	ao2_ref(infos, -1);
+	ao2_cleanup(caller_info);
+	ao2_cleanup(peer_info);
+}
+
+void ast_nova_compatibility_on_local_bridge(struct ast_multi_channel_blob *blob)
+{
+	struct ao2_container *infos = ao2_global_obj_ref(nova_channel_infos);
+	struct ast_channel_snapshot *c1;
+	struct ast_channel_snapshot *c2;
+	struct nova_channel_info *c1_info = NULL;
+	struct nova_channel_info *c2_info = NULL;
+
+	if (!infos) {
+		return;
+	}
+
+	c1 = ast_multi_channel_blob_get_channel(blob, "1");
+	c2 = ast_multi_channel_blob_get_channel(blob, "2");
+	if (!c1 || !c2) {
+		goto end;
+	}
+
+	c1_info = get_channel_info(infos, c1->base->uniqueid);
+	c2_info = get_channel_info(infos, c2->base->uniqueid);
+	if (!c1_info || !c2_info) {
+		goto end;
+	}
+
+	ao2_lock(c1_info);
+	nova_channel_info_set_local_channel_peer(c1_info, c2_info);
+	ao2_unlock(c1_info);
+
+	ao2_lock(c2_info);
+	nova_channel_info_set_local_channel_peer(c2_info, c1_info);
+	ao2_unlock(c2_info);
+
+end:
+	ao2_ref(infos, -1);
+	ao2_cleanup(c1_info);
+	ao2_cleanup(c2_info);
+}
+
+void ast_nova_compatibility_augment_bridge_enter_event(struct ast_json *extra, struct ast_bridge_snapshot *snapshot)
+{
+	struct ao2_iterator iter;
+	struct ao2_container *infos = ao2_global_obj_ref(nova_channel_infos);
+	struct ast_json *array;
+	char *uniqueid;
+
+	if (!infos) {
+		return;
+	}
+
+	array = ast_json_array_create();
+	if (!array) {
+		goto end;
+	}
+
+	iter = ao2_iterator_init(snapshot->channels, 0);
+	for (; (uniqueid = ao2_iterator_next(&iter)); ao2_ref(uniqueid, -1)) {
+		ast_json_array_append(array, get_nova_name_from_uniqueid(infos, uniqueid));
+	}
+	ao2_iterator_destroy(&iter);
+
+	ast_json_object_set(extra, "nova_channel_names", array);
+
+end:
+	ao2_ref(infos, -1);
+}
+
+void ast_nova_compatibility_augment_attended_transfer_event(struct ast_json *extra,
+	struct ast_channel_snapshot *channel2, struct ast_channel_snapshot *transferee, struct ast_channel_snapshot *transfer_target)
+{
+	struct ao2_container *infos = ao2_global_obj_ref(nova_channel_infos);
+
+	if (!infos) {
+		return;
+	}
+
+	ast_json_object_set(extra, "nova_channel2_name", get_nova_name_from_snapshot(infos, channel2));
+	ast_json_object_set(extra, "nova_transferee_name", get_nova_name_from_snapshot(infos, transferee));
+	ast_json_object_set(extra, "nova_transfer_target_name", get_nova_name_from_snapshot(infos, transfer_target));
+
+	ao2_ref(infos, -1);
+}
+
+void ast_nova_compatibility_cleanup(void)
+{
+	ao2_global_obj_release(nova_channel_infos);
+}
+
+void ast_nova_compatibility_set_enabled(int enabled)
+{
+	struct ao2_container *infos = NULL;
+
+	if (!enabled) {
+		ao2_global_obj_release(nova_channel_infos);
+	} else {
+		infos = ao2_global_obj_ref(nova_channel_infos);
+		if (!infos) {
+			infos = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, 251, nova_channel_infos_hash, NULL, nova_channel_infos_cmp);
+			if (!infos) {
+				ast_log(LOG_ERROR, "could not allocate container for CEL NOVA compatibility\n");
+				return;
+			}
+
+			ao2_global_obj_replace_unref(nova_channel_infos, infos);
+		}
+
+		ao2_ref(infos, -1);
+	}
+
+	ast_verb(3, "CEL NOVA compatibility %sabled.\n", infos ? "en" : "dis");
+}
