diff --git a/cel/cel_manager.c b/cel/cel_manager.c
index e485aab496..12671bc4a3 100644
--- a/cel/cel_manager.c
+++ b/cel/cel_manager.c
@@ -229,6 +229,7 @@ static void manager_log(struct ast_event *event)
 	struct ast_cel_event_record record = {
 		.version = AST_CEL_EVENT_RECORD_VERSION,
 	};
+	RAII_VAR(char *, tenant_id, NULL, ast_free);
 
 	if (!enablecel) {
 		return;
@@ -252,6 +253,10 @@ static void manager_log(struct ast_event *event)
 		}
 	}
 
+	if (!ast_strlen_zero(record.tenant_id)) {
+		ast_asprintf(&tenant_id, "TenantID: %s\r\n", record.tenant_id);
+	}
+
 	manager_event(EVENT_FLAG_CALL, "CEL",
 		"EventName: %s\r\n"
 		"AccountCode: %s\r\n"
@@ -269,6 +274,7 @@ static void manager_log(struct ast_event *event)
 		"AMAFlags: %s\r\n"
 		"UniqueID: %s\r\n"
 		"LinkedID: %s\r\n"
+		"%s"
 		"Userfield: %s\r\n"
 		"Peer: %s\r\n"
 		"PeerAccount: %s\r\n"
@@ -290,6 +296,7 @@ static void manager_log(struct ast_event *event)
 		ast_channel_amaflags2string(record.amaflag),
 		record.unique_id,
 		record.linked_id,
+		!ast_strlen_zero(tenant_id) ? tenant_id : "",
 		record.user_field,
 		record.peer,
 		record.peer_account,
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 52d8634348..c7d0d05fdb 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -552,19 +552,23 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s
 	struct ast_sip_channel_pvt *channel;
 	struct ast_variable *var;
 	struct ast_stream_topology *topology;
+	struct ast_channel_initializers initializers = {
+		.version = AST_CHANNEL_INITIALIZERS_VERSION,
+		.tenantid = session->endpoint->tenantid,
+	};
 	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
 
 	if (!(pvt = ao2_alloc_options(sizeof(*pvt), chan_pjsip_pvt_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
 		SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't create pvt\n");
 	}
 
-	chan = ast_channel_alloc_with_endpoint(1, state,
+	chan = ast_channel_alloc_with_initializers(1, state,
 		S_COR(session->id.number.valid, session->id.number.str, ""),
 		S_COR(session->id.name.valid, session->id.name.str, ""),
 		session->endpoint->accountcode,
 		exten, session->endpoint->context,
 		assignedids, requestor, 0,
-		session->endpoint->persistent, "PJSIP/%s-%08x",
+		session->endpoint->persistent, &initializers, "PJSIP/%s-%08x",
 		ast_sorcery_object_get_id(session->endpoint),
 		(unsigned) ast_atomic_fetchadd_int((int *) &chan_idx, +1));
 	if (!chan) {
@@ -664,7 +668,7 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s
 	for (var = session->endpoint->channel_vars; var; var = var->next) {
 		char buf[512];
 		pbx_builtin_setvar_helper(chan, var->name, ast_get_encoded_str(
-						  var->value, buf, sizeof(buf)));
+					var->value, buf, sizeof(buf)));
 	}
 
 	ast_channel_stage_snapshot_done(chan);
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index 6b8936b2d4..61c8846b8f 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -998,6 +998,13 @@
                 ; AOC updates can be sent using the AOCMessage AMI action or come
                 ; from PRI channels.
                 ; (default: no)
+;
+; tenantid =
+                ; Sets the tenant ID for this endpoint. It can be read in dialplan
+                ; with the CHANNEL function, and it can be changed later via dialplan
+                ; using the same CHANNEL function if needed. Setting tenant ID here
+                ; will cause it to show up on channel creation and the initial
+                ; channel snapshot.
 
 
 ;==========================AUTH SECTION OPTIONS=========================
diff --git a/contrib/ast-db-manage/config/versions/655054a68ad5_add_pjsip_tenantid.py b/contrib/ast-db-manage/config/versions/655054a68ad5_add_pjsip_tenantid.py
new file mode 100644
index 0000000000..f64a8d8089
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/655054a68ad5_add_pjsip_tenantid.py
@@ -0,0 +1,22 @@
+"""add pjsip tenantid
+
+Revision ID: 655054a68ad5
+Revises: bd9c5159c7ea
+Create Date: 2024-06-11 11:18:41.466929
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '655054a68ad5'
+down_revision = '2b7c507d7d12'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    op.add_column('ps_endpoints', sa.Column('tenantid', sa.String(80)))
+
+
+def downgrade():
+    op.drop_column('ps_endpoints', 'tenantid')
diff --git a/funcs/func_channel.c b/funcs/func_channel.c
index 2ee5165751..19c7243080 100644
--- a/funcs/func_channel.c
+++ b/funcs/func_channel.c
@@ -273,6 +273,9 @@
 					<enum name="linkedid">
 						<para>R/O returns the linkedid if available, otherwise returns the uniqueid.</para>
 					</enum>
+					<enum name="tenantid">
+						<para>R/W The channel tenantid.</para>
+					</enum>
 					<enum name="max_forwards">
 						<para>R/W The maximum number of forwards allowed.</para>
 					</enum>
@@ -565,6 +568,8 @@ static int func_channel_read(struct ast_channel *chan, const char *function,
 		}
 	} else if (!strcasecmp(data, "device_name")) {
 		ret = ast_channel_get_device_name(chan, buf, len);
+	} else if (!strcasecmp(data, "tenantid")) {
+		locked_copy_string(chan, buf, ast_channel_tenantid(chan), len);
 	} else if (!ast_channel_tech(chan) || !ast_channel_tech(chan)->func_channel_read || ast_channel_tech(chan)->func_channel_read(chan, function, data, buf, len)) {
 		ast_log(LOG_WARNING, "Unknown or unavailable item requested: '%s'\n", data);
 		ret = -1;
@@ -737,6 +742,8 @@ static int func_channel_write_real(struct ast_channel *chan, const char *functio
 			ret = ast_max_forwards_set(chan, max_forwards);
 			ast_channel_unlock(chan);
 		}
+	} else if (!strcasecmp(data, "tenantid")) {
+		ast_channel_tenantid_set(chan, value);
 	} else if (!ast_channel_tech(chan)->func_channel_write
 		 || ast_channel_tech(chan)->func_channel_write(chan, function, data, value)) {
 		ast_log(LOG_WARNING, "Unknown or unavailable item requested: '%s'\n",
diff --git a/include/asterisk/cdr.h b/include/asterisk/cdr.h
index 06307c91f2..788a879720 100644
--- a/include/asterisk/cdr.h
+++ b/include/asterisk/cdr.h
@@ -317,6 +317,10 @@ struct ast_cdr {
 	char uniqueid[AST_MAX_UNIQUEID];
 	/*! Linked group Identifier */
 	char linkedid[AST_MAX_UNIQUEID];
+	/*! Channel tenant Identifier */
+	char tenantid[AST_MAX_TENANT_ID];
+	/*! Channel tenant Identifier of the last person we talked to */
+	char peertenantid[AST_MAX_TENANT_ID];
 	/*! User field */
 	char userfield[AST_MAX_USER_FIELD];
 	/*! Sequence field */
diff --git a/include/asterisk/cel.h b/include/asterisk/cel.h
index 81f375be77..7444938ce6 100644
--- a/include/asterisk/cel.h
+++ b/include/asterisk/cel.h
@@ -140,7 +140,7 @@ struct ast_cel_event_record {
 	 * \brief struct ABI version
 	 * \note This \b must be incremented when the struct changes.
 	 */
-	#define AST_CEL_EVENT_RECORD_VERSION 2
+	#define AST_CEL_EVENT_RECORD_VERSION 3
 	/*!
 	 * \brief struct ABI version
 	 * \note This \b must stay as the first member.
@@ -164,6 +164,7 @@ struct ast_cel_event_record {
 	const char *peer_account;
 	const char *unique_id;
 	const char *linked_id;
+	const char *tenant_id;
 	uint amaflag;
 	const char *user_field;
 	const char *peer;
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 99114366b3..2691af19b0 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -146,6 +146,8 @@ extern "C" {
  */
 #define AST_MAX_PUBLIC_UNIQUEID 149
 
+#define AST_MAX_TENANT_ID 64 /*!< Max length of a channel tenant_id */
+
 /*!
  * The number of buckets to store channels or channel information
  */
@@ -606,6 +608,24 @@ struct ast_assigned_ids {
 	const char *uniqueid2;
 };
 
+/*!
+ * \brief Helper struct for initializing additional channel information on channel creation.
+ * \since 18.25.0
+ */
+struct ast_channel_initializers {
+	/*!
+	 * \brief struct ABI version
+	 * \note This \b must be incremented when the struct changes.
+	 */
+	#define AST_CHANNEL_INITIALIZERS_VERSION 1
+	/*!
+	 * \brief struct ABI version
+	 * \note This \b must stay as the first member.
+	 */
+	uint32_t version;
+	const char *tenantid;
+};
+
 /*!
  * \brief Forward declaration
  */
@@ -1244,6 +1264,27 @@ struct ast_channel * __attribute__((format(printf, 15, 16)))
 		const char *file, int line, const char *function,
 		const char *name_fmt, ...);
 
+/*!
+ * \brief Create a channel structure
+ * \since 18.25.0
+ *
+ * \retval NULL failure
+ * \retval non-NULL successfully allocated channel
+ *
+ * \note Absolutely _NO_ channel locks should be held before calling this function.
+ * \note By default, new channels are set to the "s" extension
+ *       and "default" context.
+ * \note Same as __ast_channel_alloc but with ast_channel_initializers struct.
+ */
+struct ast_channel * __attribute__((format(printf, 16, 17)))
+	__ast_channel_alloc_with_initializers(int needqueue, int state, const char *cid_num,
+		const char *cid_name, const char *acctcode,
+		const char *exten, const char *context, const struct ast_assigned_ids *assignedids,
+		const struct ast_channel *requestor, enum ama_flags amaflag,
+		struct ast_endpoint *endpoint, struct ast_channel_initializers *initializers,
+		const char *file, int line, const char *function,
+		const char *name_fmt, ...);
+
 /*!
  * \brief Create a channel structure
  *
@@ -1263,6 +1304,11 @@ struct ast_channel * __attribute__((format(printf, 15, 16)))
 	__ast_channel_alloc((needqueue), (state), (cid_num), (cid_name), (acctcode), (exten), (context), (assignedids), (requestor), (amaflag), (endpoint), \
 		__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
 
+#define ast_channel_alloc_with_initializers(needqueue, state, cid_num, cid_name, acctcode, exten, context, assignedids, requestor, amaflag, endpoint, initializers, ...) \
+	__ast_channel_alloc_with_initializers((needqueue), (state), (cid_num), (cid_name), (acctcode), (exten), (context), (assignedids), (requestor), (amaflag), (endpoint), \
+		(initializers), __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
+
+
 /*!
  * \brief Create a fake channel structure
  *
@@ -4073,6 +4119,8 @@ const char *ast_channel_userfield(const struct ast_channel *chan);
 const char *ast_channel_call_forward(const struct ast_channel *chan);
 const char *ast_channel_uniqueid(const struct ast_channel *chan);
 const char *ast_channel_linkedid(const struct ast_channel *chan);
+const char *ast_channel_tenantid(const struct ast_channel *chan);
+void ast_channel_tenantid_set(struct ast_channel *chan, const char *value);
 const char *ast_channel_parkinglot(const struct ast_channel *chan);
 const char *ast_channel_hangupsource(const struct ast_channel *chan);
 const char *ast_channel_dialcontext(const struct ast_channel *chan);
diff --git a/include/asterisk/channel_internal.h b/include/asterisk/channel_internal.h
index 774c9b03c1..1b994fa9b4 100644
--- a/include/asterisk/channel_internal.h
+++ b/include/asterisk/channel_internal.h
@@ -23,6 +23,8 @@
 
 #define ast_channel_internal_alloc(destructor, assignedid, requestor) __ast_channel_internal_alloc(destructor, assignedid, requestor, __FILE__, __LINE__, __PRETTY_FUNCTION__)
 struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *file, int line, const char *function);
+struct ast_channel *__ast_channel_internal_alloc_with_initializers(void (*destructor)(void *obj), const struct ast_assigned_ids *assignedids,
+	const struct ast_channel *requestor, const struct ast_channel_initializers *initializers, const char *file, int line, const char *function);
 void ast_channel_internal_finalize(struct ast_channel *chan);
 int ast_channel_internal_is_finalized(struct ast_channel *chan);
 void ast_channel_internal_cleanup(struct ast_channel *chan);
diff --git a/include/asterisk/event_defs.h b/include/asterisk/event_defs.h
index b9b87e25ff..d4e0033f6a 100644
--- a/include/asterisk/event_defs.h
+++ b/include/asterisk/event_defs.h
@@ -311,8 +311,15 @@ enum ast_event_ie_type {
 	 * Payload type: UINT
 	 */
 	AST_EVENT_IE_NODE_ID             = 0x003e,
+
+	/*!
+	 * \brief Channel Event TenantID
+	 * Used by: AST_EVENT_CEL
+	 * Payload type: STR
+	 */
+	AST_EVENT_IE_CEL_TENANTID	= 0x003f,
 	/*! \brief Must be the last IE value +1 */
-	AST_EVENT_IE_TOTAL               = 0x003f,
+	AST_EVENT_IE_TOTAL               = 0x0040,
 };
 
 /*!
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index d0e1e59223..dc66a40184 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -987,6 +987,8 @@ struct ast_sip_endpoint {
 		AST_STRING_FIELD(incoming_mwi_mailbox);
 		/*! STIR/SHAKEN profile to use */
 		AST_STRING_FIELD(stir_shaken_profile);
+		/*! Tenant ID for the endpoint */
+		AST_STRING_FIELD(tenantid);
 	);
 	/*! Configuration for extensions */
 	struct ast_sip_endpoint_extensions extensions;
diff --git a/include/asterisk/stasis_channels.h b/include/asterisk/stasis_channels.h
index 728e0ff1e3..f56545f5ef 100644
--- a/include/asterisk/stasis_channels.h
+++ b/include/asterisk/stasis_channels.h
@@ -109,6 +109,7 @@ struct ast_channel_snapshot_base {
 		AST_STRING_FIELD(userfield);   /*!< Userfield for CEL billing */
 		AST_STRING_FIELD(language);    /*!< The default spoken language for the channel */
 		AST_STRING_FIELD(type);        /*!< Type of channel technology */
+		AST_STRING_FIELD(tenantid);    /*!< Channel tenant identifier */
 	);
 	struct timeval creationtime; /*!< The time of channel creation */
 	int tech_properties;         /*!< Properties of the channel's technology */
diff --git a/main/cdr.c b/main/cdr.c
index 115316b96c..fb43c87d94 100644
--- a/main/cdr.c
+++ b/main/cdr.c
@@ -765,7 +765,8 @@ struct cdr_object {
 	struct ast_flags flags;                 /*!< Flags on the CDR */
 	AST_DECLARE_STRING_FIELDS(
 		AST_STRING_FIELD(linkedid);         /*!< Linked ID. Cached here as it may change out from party A, which must be immutable */
-		AST_STRING_FIELD(uniqueid);			/*!< Unique id of party A. Cached here as it is the master CDR container key */
+		AST_STRING_FIELD(uniqueid);         /*!< Unique id of party A. Cached here as it is the master CDR container key */
+		AST_STRING_FIELD(tenantid);         /*!< Tenant ID. Cached here because the value can be manipulated through dialplan */
 		AST_STRING_FIELD(name);             /*!< Channel name of party A. Cached here as the party A address may change */
 		AST_STRING_FIELD(bridge);           /*!< The bridge the party A happens to be in. */
 		AST_STRING_FIELD(appl);             /*!< The last accepted application party A was in */
@@ -1094,6 +1095,7 @@ static struct cdr_object *cdr_object_alloc(struct ast_channel_snapshot *chan, co
 	ast_string_field_set(cdr, uniqueid, chan->base->uniqueid);
 	ast_string_field_set(cdr, name, chan->base->name);
 	ast_string_field_set(cdr, linkedid, chan->peer->linkedid);
+	ast_string_field_set(cdr, tenantid, chan->base->tenantid);
 	cdr->disposition = AST_CDR_NULL;
 	cdr->sequence = ast_atomic_fetchadd_int(&global_cdr_sequence, +1);
 	cdr->lastevent = *event_time;
@@ -1362,6 +1364,7 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
 		ast_copy_string(cdr_copy->lastdata, it_cdr->data, sizeof(cdr_copy->lastdata));
 		ast_copy_string(cdr_copy->dst, it_cdr->exten, sizeof(cdr_copy->dst));
 		ast_copy_string(cdr_copy->dcontext, it_cdr->context, sizeof(cdr_copy->dcontext));
+		ast_copy_string(cdr_copy->tenantid, it_cdr->tenantid, sizeof(cdr_copy->tenantid));
 
 		/* Party B */
 		if (party_b) {
@@ -1370,6 +1373,7 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
 			if (!ast_strlen_zero(it_cdr->party_b.userfield)) {
 				snprintf(cdr_copy->userfield, sizeof(cdr_copy->userfield), "%s;%s", it_cdr->party_a.userfield, it_cdr->party_b.userfield);
 			}
+			ast_copy_string(cdr_copy->peertenantid, party_b->base->tenantid, sizeof(cdr_copy->peertenantid));
 		}
 		if (ast_strlen_zero(cdr_copy->userfield) && !ast_strlen_zero(it_cdr->party_a.userfield)) {
 			ast_copy_string(cdr_copy->userfield, it_cdr->party_a.userfield, sizeof(cdr_copy->userfield));
@@ -3166,6 +3170,10 @@ void ast_cdr_format_var(struct ast_cdr *cdr, const char *name, char **ret, char
 		ast_copy_string(workspace, cdr->uniqueid, workspacelen);
 	} else if (!strcasecmp(name, "linkedid")) {
 		ast_copy_string(workspace, cdr->linkedid, workspacelen);
+	} else if (!strcasecmp(name, "tenantid")) {
+		ast_copy_string(workspace, cdr->tenantid, workspacelen);
+	} else if (!strcasecmp(name, "peertenantid")) {
+		ast_copy_string(workspace, cdr->peertenantid, workspacelen);
 	} else if (!strcasecmp(name, "userfield")) {
 		ast_copy_string(workspace, cdr->userfield, workspacelen);
 	} else if (!strcasecmp(name, "sequence")) {
@@ -3232,6 +3240,7 @@ static const char * const cdr_readonly_vars[] = {
 	"accountcode",
 	"uniqueid",
 	"linkedid",
+	"tenantid",
 	"userfield",
 	"sequence",
 	NULL
@@ -3353,6 +3362,14 @@ static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *na
 		ast_copy_string(value, party_a->base->uniqueid, length);
 	} else if (!strcasecmp(name, "linkedid")) {
 		ast_copy_string(value, cdr_obj->linkedid, length);
+	} else if (!strcasecmp(name, "tenantid")) {
+		ast_copy_string(value, party_a->base->tenantid, length);
+	} else if (!strcasecmp(name, "peertenantid")) {
+		if (party_b) {
+			ast_copy_string(value, party_b->base->tenantid, length);
+		} else {
+			ast_copy_string(value, "", length);
+		}
 	} else if (!strcasecmp(name, "userfield")) {
 		ast_copy_string(value, cdr_obj->party_a.userfield, length);
 	} else if (!strcasecmp(name, "sequence")) {
diff --git a/main/cel.c b/main/cel.c
index d41543c27a..98d31b551a 100644
--- a/main/cel.c
+++ b/main/cel.c
@@ -554,6 +554,7 @@ struct ast_event *ast_cel_create_event_with_time(struct ast_channel_snapshot *sn
 		AST_EVENT_IE_CEL_PEERACCT, AST_EVENT_IE_PLTYPE_STR, snapshot->peer->account,
 		AST_EVENT_IE_CEL_UNIQUEID, AST_EVENT_IE_PLTYPE_STR, snapshot->base->uniqueid,
 		AST_EVENT_IE_CEL_LINKEDID, AST_EVENT_IE_PLTYPE_STR, snapshot->peer->linkedid,
+		AST_EVENT_IE_CEL_TENANTID, AST_EVENT_IE_PLTYPE_STR, snapshot->base->tenantid,
 		AST_EVENT_IE_CEL_USERFIELD, AST_EVENT_IE_PLTYPE_STR, snapshot->base->userfield,
 		AST_EVENT_IE_CEL_EXTRA, AST_EVENT_IE_PLTYPE_STR, S_OR(extra_txt, ""),
 		AST_EVENT_IE_CEL_PEER, AST_EVENT_IE_PLTYPE_STR, S_OR(peer, ""),
@@ -853,6 +854,7 @@ int ast_cel_fill_record(const struct ast_event *e, struct ast_cel_event_record *
 	r->peer_account     = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_PEERACCT), "");
 	r->unique_id        = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_UNIQUEID), "");
 	r->linked_id        = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_LINKEDID), "");
+	r->tenant_id        = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_TENANTID), "");
 	r->amaflag          = ast_event_get_ie_uint(e, AST_EVENT_IE_CEL_AMAFLAGS);
 	r->user_field       = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_USERFIELD), "");
 	r->peer             = S_OR(ast_event_get_ie_str(e, AST_EVENT_IE_CEL_PEER), "");
diff --git a/main/channel.c b/main/channel.c
index d8741b3a32..2b9f192612 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -733,7 +733,7 @@ static struct ast_channel *__attribute__((format(printf, 15, 0)))
 __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char *cid_name,
 		       const char *acctcode, const char *exten, const char *context, const struct ast_assigned_ids *assignedids,
 		       const struct ast_channel *requestor, enum ama_flags amaflag, struct ast_endpoint *endpoint,
-		       const char *file, int line,
+		       struct ast_channel_initializers *initializers, const char *file, int line,
 		       const char *function, const char *name_fmt, va_list ap)
 {
 	struct ast_channel *tmp;
@@ -752,7 +752,7 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
 		return NULL;
 	}
 
-	tmp = __ast_channel_internal_alloc(ast_channel_destructor, assignedids, requestor,
+	tmp = __ast_channel_internal_alloc_with_initializers(ast_channel_destructor, assignedids, requestor, initializers,
 		file, line, function);
 	if (!tmp) {
 		/* Channel structure allocation failure. */
@@ -964,7 +964,26 @@ struct ast_channel *__ast_channel_alloc(int needqueue, int state, const char *ci
 
 	va_start(ap, name_fmt);
 	result = __ast_channel_alloc_ap(needqueue, state, cid_num, cid_name, acctcode, exten, context,
-					assignedids, requestor, amaflag, endpoint, file, line, function, name_fmt, ap);
+					assignedids, requestor, amaflag, endpoint, NULL, file, line, function, name_fmt, ap);
+	va_end(ap);
+
+	return result;
+}
+
+struct ast_channel *__ast_channel_alloc_with_initializers(int needqueue, int state, const char *cid_num,
+					const char *cid_name, const char *acctcode,
+					const char *exten, const char *context, const struct ast_assigned_ids *assignedids,
+					const struct ast_channel *requestor, enum ama_flags amaflag,
+					struct ast_endpoint *endpoint, struct ast_channel_initializers *initializers,
+					const char *file, int line, const char *function,
+					const char *name_fmt, ...)
+{
+	va_list ap;
+	struct ast_channel *result;
+
+	va_start(ap, name_fmt);
+	result = __ast_channel_alloc_ap(needqueue, state, cid_num, cid_name, acctcode, exten, context,
+					assignedids, requestor, amaflag, endpoint, initializers, file, line, function, name_fmt, ap);
 	va_end(ap);
 
 	return result;
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index 8776696c1b..ee9f48fcb6 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -57,6 +57,7 @@ struct ast_channel_id {
 	time_t creation_time;				/*!< Creation time */
 	int creation_unique;				/*!< sub-second unique value */
 	char unique_id[AST_MAX_UNIQUEID];	/*!< Unique Identifier */
+	char tenant_id[AST_MAX_TENANT_ID];	/*!< Multi-tenant identifier */
 };
 
 /*!
@@ -312,6 +313,21 @@ const char *ast_channel_linkedid(const struct ast_channel *chan)
 	return chan->linkedid.unique_id;
 }
 
+const char *ast_channel_tenantid(const struct ast_channel *chan)
+{
+	/* It's ok for tenantid to be empty, so no need to assert */
+	return chan->linkedid.tenant_id;
+}
+
+void ast_channel_tenantid_set(struct ast_channel *chan, const char *value)
+{
+	if (ast_strlen_zero(value)) {
+		return;
+	}
+	ast_copy_string(chan->linkedid.tenant_id, value, sizeof(chan->linkedid.tenant_id));
+	ast_channel_snapshot_invalidate_segment(chan, AST_CHANNEL_SNAPSHOT_INVALIDATE_BASE);
+}
+
 const char *ast_channel_appl(const struct ast_channel *chan)
 {
 	return chan->appl;
@@ -1314,7 +1330,8 @@ static int pvt_cause_cmp_fn(void *obj, void *vstr, int flags)
 
 #define DIALED_CAUSES_BUCKETS 37
 
-struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *file, int line, const char *function)
+struct ast_channel *__ast_channel_internal_alloc_with_initializers(void (*destructor)(void *obj), const struct ast_assigned_ids *assignedids,
+	const struct ast_channel *requestor, const struct ast_channel_initializers *initializers, const char *file, int line, const char *function)
 {
 	struct ast_channel *tmp;
 
@@ -1335,6 +1352,20 @@ struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj),
 		return ast_channel_unref(tmp);
 	}
 
+	/* Check initializers validity here for early abort. Unfortunately, we can't do much here because
+	 * tenant ID is part of linked ID, which would overwrite it further down. */
+	if (initializers) {
+		if (initializers->version == 0) {
+			ast_log(LOG_ERROR, "Channel initializers must have a non-zero version.\n");
+			return ast_channel_unref(tmp);
+		} else if (initializers->version != AST_CHANNEL_INITIALIZERS_VERSION) {
+			ast_log(LOG_ERROR, "ABI mismatch for ast_channel_initializers. "
+				"Please ensure all modules were compiled for "
+				"this version of Asterisk.\n");
+			return ast_channel_unref(tmp);
+		}
+	}
+
 	/* set the creation time in the uniqueid */
 	tmp->uniqueid.creation_time = time(NULL);
 	tmp->uniqueid.creation_unique = ast_atomic_fetchadd_int(&uniqueint, 1);
@@ -1360,6 +1391,12 @@ struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj),
 		tmp->linkedid = tmp->uniqueid;
 	}
 
+	/* Things like tenant ID need to be set here, otherwise they would be overwritten by
+	 * things like inheriting linked ID above. */
+	if (initializers) {
+		ast_copy_string(tmp->linkedid.tenant_id, initializers->tenantid, sizeof(tmp->linkedid.tenant_id));
+	}
+
 	AST_VECTOR_INIT(&tmp->fds, AST_MAX_FDS);
 
 	/* Force all channel snapshot segments to be created on first use, so we don't have to check if
@@ -1370,6 +1407,12 @@ struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj),
 	return tmp;
 }
 
+struct ast_channel *__ast_channel_internal_alloc(void (*destructor)(void *obj), const struct ast_assigned_ids *assignedids,
+	const struct ast_channel *requestor, const char *file, int line, const char *function)
+{
+	return __ast_channel_internal_alloc_with_initializers(destructor, assignedids, requestor, NULL, file, line, function);
+}
+
 struct ast_channel *ast_channel_internal_oldest_linkedid(struct ast_channel *a, struct ast_channel *b)
 {
 	ast_assert(a->linkedid.creation_time != 0);
diff --git a/main/cli.c b/main/cli.c
index 19355ab6db..db9a86e574 100644
--- a/main/cli.c
+++ b/main/cli.c
@@ -1659,6 +1659,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 	ast_callid callid;
 	char callid_buf[32];
 	int stream_num;
+	RAII_VAR(char *, tenant_id, NULL, ast_free);
 
 	switch (cmd) {
 	case CLI_INIT:
@@ -1717,12 +1718,17 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 		ast_callid_strnprint(callid_buf, sizeof(callid_buf), callid);
 	}
 
+	if (!ast_strlen_zero(ast_channel_tenantid(chan))) {
+		ast_asprintf(&tenant_id, "       TenantID: %s\n", ast_channel_tenantid(chan));
+	}
+
 	ast_str_append(&output, 0,
 		" -- General --\n"
 		"           Name: %s\n"
 		"           Type: %s\n"
 		"       UniqueID: %s\n"
 		"       LinkedID: %s\n"
+		"%s"
 		"      Caller ID: %s\n"
 		" Caller ID Name: %s\n"
 		"Connected Line ID: %s\n"
@@ -1753,6 +1759,7 @@ static char *handle_showchan(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 		ast_channel_tech(chan)->type,
 		ast_channel_uniqueid(chan),
 		ast_channel_linkedid(chan),
+		!ast_strlen_zero(tenant_id) ? tenant_id : "",
 		S_COR(ast_channel_caller(chan)->id.number.valid,
 		      ast_channel_caller(chan)->id.number.str, "(N/A)"),
 		S_COR(ast_channel_caller(chan)->id.name.valid,
diff --git a/main/event.c b/main/event.c
index 2b73cd7385..7c8af91475 100644
--- a/main/event.c
+++ b/main/event.c
@@ -189,6 +189,7 @@ static const struct ie_map {
 	[AST_EVENT_IE_PRESENCE_STATE]      = { AST_EVENT_IE_PLTYPE_UINT, "PresenceState" },
 	[AST_EVENT_IE_PRESENCE_SUBTYPE]    = { AST_EVENT_IE_PLTYPE_STR,  "PresenceSubtype" },
 	[AST_EVENT_IE_PRESENCE_MESSAGE]    = { AST_EVENT_IE_PLTYPE_STR,  "PresenceMessage" },
+	[AST_EVENT_IE_CEL_TENANTID]        = { AST_EVENT_IE_PLTYPE_STR, "TenantID" },
 };
 
 const char *ast_event_get_type_name(const struct ast_event *event)
diff --git a/main/manager_channels.c b/main/manager_channels.c
index 57bf828088..76fd3157bb 100644
--- a/main/manager_channels.c
+++ b/main/manager_channels.c
@@ -517,6 +517,10 @@ struct ast_str *ast_manager_build_channel_state_string_prefix(
 		return NULL;
 	}
 
+	if (!ast_strlen_zero(snapshot->base->tenantid)) {
+		ast_str_append(&out, 0, "%sTenantid: %s\r\n", prefix, snapshot->base->tenantid);
+	}
+
 	if (snapshot->manager_vars) {
 		struct ast_var_t *var;
 		char *val;
diff --git a/main/stasis_channels.c b/main/stasis_channels.c
index 4b48a9c9be..af2bb1f810 100644
--- a/main/stasis_channels.c
+++ b/main/stasis_channels.c
@@ -285,6 +285,7 @@ static struct ast_channel_snapshot_base *channel_snapshot_base_create(struct ast
 	ast_string_field_set(snapshot, userfield, ast_channel_userfield(chan));
 	ast_string_field_set(snapshot, uniqueid, ast_channel_uniqueid(chan));
 	ast_string_field_set(snapshot, language, ast_channel_language(chan));
+	ast_string_field_set(snapshot, tenantid, ast_channel_tenantid(chan));
 
 	snapshot->creationtime = ast_channel_creationtime(chan);
 	snapshot->tech_properties = ast_channel_tech(chan)->properties;
@@ -1329,6 +1330,10 @@ struct ast_json *ast_channel_snapshot_to_json(
 		ast_json_object_set(json_chan, "channelvars", ast_json_channel_vars(snapshot->ari_vars));
 	}
 
+        if (!ast_strlen_zero(snapshot->base->tenantid)) {
+                ast_json_object_set(json_chan, "tenantid", ast_json_string_create(snapshot->base->tenantid));
+        }
+
 	return json_chan;
 }
 
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index a5ba5cc768..e7773d2683 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -1165,6 +1165,15 @@ int ast_ari_validate_channel(struct ast_json *json)
 				res = 0;
 			}
 		} else
+		if (strcmp("tenantid", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Channel field tenantid failed validation\n");
+				res = 0;
+			}
+		} else
 		{
 			ast_log(LOG_ERROR,
 				"ARI Channel has undocumented field %s\n",
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index f350c8de33..39df8b4866 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -1356,6 +1356,7 @@ ari_validator ast_ari_validate_application_fn(void);
  * - name: string (required)
  * - protocol_id: string (required)
  * - state: string (required)
+ * - tenantid: string
  * Dialed
  * DialplanCEP
  * - app_data: string (required)
diff --git a/res/res_pjsip/pjsip_config.xml b/res/res_pjsip/pjsip_config.xml
index 753037b555..ab1c6332f3 100644
--- a/res/res_pjsip/pjsip_config.xml
+++ b/res/res_pjsip/pjsip_config.xml
@@ -681,6 +681,14 @@
 						dialplan application such as <emphasis>Ringing</emphasis>.</para>
 					</description>
 				</configOption>
+				<configOption name="tenantid" default="">
+					<synopsis>The tenant ID for this endpoint.</synopsis>
+					<description><para>
+						Sets the tenant ID for this endpoint. When a channel is created,
+						tenantid will be set to this value. It can be changed via dialplan
+						later if needed.
+					</para></description>
+				</configOption>
 				<configOption name="timers_min_se" default="90">
 					<synopsis>Minimum session timers expiration period</synopsis>
 					<description><para>
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 58a50c484a..c83ee33179 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -2303,6 +2303,7 @@ int ast_res_pjsip_initialize_configuration(void)
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "security_mechanisms", "", security_mechanism_handler, security_mechanism_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "security_negotiation", "no", security_negotiation_handler, security_negotiation_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_aoc", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, send_aoc));
+	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "tenantid", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, tenantid));
 
 	if (ast_sip_initialize_sorcery_transport()) {
 		ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index 3f8e173a62..ab4a0421c9 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -2191,6 +2191,11 @@
 				"caller_rdnis": {
 					"type": "string",
 					"description": "The Caller ID RDNIS"
+				},
+				"tenantid": {
+					"required": false,
+					"type": "string",
+					"description": "The Tenant ID for the channel"
 				}
 			}
 		}