Index: b/main/channel.c
===================================================================
--- a/main/channel.c
+++ b/main/channel.c
@@ -74,6 +74,7 @@
 #include "asterisk/max_forwards.h"
 #include "asterisk/stream.h"
 #include "asterisk/message.h"
+#include "asterisk/cel.h"
 
 /*** DOCUMENTATION
  ***/
@@ -3546,6 +3547,7 @@ static struct ast_frame *__ast_read(stru
 	struct ast_frame *f = NULL;	/* the return value */
 	int prestate;
 	int cause = 0;
+	enum ast_cel_event_type event_type = 0;
 	struct ast_stream *stream = NULL, *default_stream = NULL;
 
 	/* this function is very long so make sure there is only one return
@@ -3881,6 +3883,10 @@ static struct ast_frame *__ast_read(stru
 				}
 				ast_frfree(f);
 				f = &ast_null_frame;
+			} else if (f->subclass.integer == AST_CONTROL_HOLD) {
+				event_type = AST_CEL_HOLD;
+			} else if (f->subclass.integer == AST_CONTROL_UNHOLD) {
+				event_type = AST_CEL_UNHOLD;
 			} else if (f->subclass.integer == AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE && dropnondefault) {
 				/* The caller of this function is incapable of handling streams so we don't accept the change request
 				 * and stick to the streams currently on the channel.
@@ -4270,6 +4276,11 @@ done:
 		ast_channel_audiohooks_set(chan, NULL);
 	}
 	ast_channel_unlock(chan);
+
+	if (event_type) {
+		ast_cel_publish_event(chan, event_type, ast_json_null());
+	}
+
 	return f;
 }
 
Index: b/configs/samples/cel.conf.sample
===================================================================
--- a/configs/samples/cel.conf.sample
+++ b/configs/samples/cel.conf.sample
@@ -59,6 +59,8 @@ apps=dial,park
 ;  USER_DEFINED     -- Triggered from the dialplan, and has a name given by the
 ;                      user
 ;  LOCAL_OPTIMIZE   -- A local channel pair is optimizing away.
+;  HOLD             -- The time a hold control frame was read
+;  UNHOLD           -- The time an unhold control frame was read
 ;
 ; Default value: none
 ;                (Track no events)
Index: b/include/asterisk/cel.h
===================================================================
--- a/include/asterisk/cel.h
+++ b/include/asterisk/cel.h
@@ -77,6 +77,10 @@ enum ast_cel_event_type {
 	AST_CEL_LOCAL_OPTIMIZE = 17,
 	/*! \brief A local channel optimization has begun */
 	AST_CEL_LOCAL_OPTIMIZE_BEGIN = 18,
+	/*! \brief a AST_CONTROL_HOLD frame is read from a channel */
+	AST_CEL_HOLD = 19,
+	/*! \brief a AST_CONTROL_UNHOLD frame is read from a channel */
+	AST_CEL_UNHOLD = 20,
 };
 
 /*!
Index: b/main/cel.c
===================================================================
--- a/main/cel.c
+++ b/main/cel.c
@@ -328,6 +328,8 @@ static const char * const cel_event_type
 	[AST_CEL_LINKEDID_END]     = "LINKEDID_END",
 	[AST_CEL_LOCAL_OPTIMIZE]   = "LOCAL_OPTIMIZE",
 	[AST_CEL_LOCAL_OPTIMIZE_BEGIN]   = "LOCAL_OPTIMIZE_BEGIN",
+	[AST_CEL_HOLD]             = "HOLD",
+	[AST_CEL_UNHOLD]           = "UNHOLD",
 };
 
 struct cel_backend {
@@ -1279,6 +1281,11 @@ static void cel_generic_cb(
 				event, extra, NULL);
 			break;
 		}
+	case AST_CEL_HOLD:
+	case AST_CEL_UNHOLD:
+		cel_report_event(obj->snapshot, event_type, stasis_message_timestamp(message),
+			NULL, NULL, NULL);
+		break;
 	default:
 		ast_log(LOG_ERROR, "Unhandled %s event blob\n", ast_cel_get_type_name(event_type));
 		break;
