diff --git a/frontends/php/actionconf.php b/frontends/php/actionconf.php
index 6d43739..16135a7 100644
--- a/frontends/php/actionconf.php
+++ b/frontends/php/actionconf.php
@@ -36,7 +36,7 @@ $fields = array(
 	'actionid' =>			array(T_ZBX_INT, O_OPT, P_SYS,	DB_ID,		'isset({form})&&{form}=="update"'),
 	'name' =>				array(T_ZBX_STR, O_OPT, null,	NOT_EMPTY,	'isset({save})', _('Name')),
 	'eventsource' =>		array(T_ZBX_INT, O_OPT, null,
-		IN(array(EVENT_SOURCE_TRIGGERS, EVENT_SOURCE_DISCOVERY, EVENT_SOURCE_AUTO_REGISTRATION, EVENT_SOURCE_INTERNAL)),
+		IN(array(EVENT_SOURCE_TRIGGERS, EVENT_SOURCE_DISCOVERY, EVENT_SOURCE_AUTO_REGISTRATION, EVENT_SOURCE_INTERNAL, EVENT_SOURCE_SERVICES)),
 		null
 	),
 	'evaltype' =>			array(T_ZBX_INT, O_OPT, null,
@@ -423,6 +423,12 @@ if (hasRequest('form')) {
 				$data['action']['def_shortdata'] = get_request('def_shortdata', ACTION_DEFAULT_SUBJ_AUTOREG);
 				$data['action']['def_longdata'] = get_request('def_longdata', ACTION_DEFAULT_MSG_AUTOREG);
 			}
+			elseif ($data['eventsource'] == EVENT_SOURCE_SERVICES) {
+				$data['action']['def_shortdata'] = get_request('def_shortdata', ACTION_DEFAULT_SUBJ_SERVICE);
+				$data['action']['def_longdata'] = get_request('def_longdata', ACTION_DEFAULT_MSG_SERVICE);
+				$data['action']['r_shortdata'] = get_request('r_shortdata', ACTION_DEFAULT_SUBJ_SERVICE);
+				$data['action']['r_longdata'] = get_request('r_longdata', ACTION_DEFAULT_MSG_SERVICE);
+			}
 			else {
 				$data['action']['def_shortdata'] = get_request('def_shortdata');
 				$data['action']['def_longdata'] = get_request('def_longdata');
diff --git a/frontends/php/api/classes/CAction.php b/frontends/php/api/classes/CAction.php
index bf23c18..69482d6 100644
--- a/frontends/php/api/classes/CAction.php
+++ b/frontends/php/api/classes/CAction.php
@@ -1493,6 +1493,7 @@ class CAction extends CZBXAPI {
 		$discoveryRuleIdsAll = array();
 		$proxyIdsAll = array();
 		$proxyidsAll = array();
+		$serviceIdsAll = array();
 
 		// build validators
 		$timePeriodValidator = new CTimePeriodValidator();
@@ -1679,6 +1680,13 @@ class CAction extends CZBXAPI {
 						}
 						break;
 
+					case CONDITION_TYPE_SERVICE:
+						if (!$condition['value']) {
+							self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty action condition.'));
+						}
+						$serviceIdsAll[$condition['value']] = $condition['value'];
+						break;
+
 					default:
 						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action condition type.'));
 						break;
@@ -1707,6 +1715,9 @@ class CAction extends CZBXAPI {
 		if (!API::Proxy()->isWritable($proxyidsAll)) {
 			self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action condition proxy. Proxy does not exist or you have no access to it.'));
 		}
+		if (!API::Service()->isWritable($serviceIdsAll)) {
+			self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect action condition service. IT service does not exist or you have no access to it.'));
+		}
 
 		return true;
 	}
diff --git a/frontends/php/include/actions.inc.php b/frontends/php/include/actions.inc.php
index 5ffb5a1..7cb37eb 100644
--- a/frontends/php/include/actions.inc.php
+++ b/frontends/php/include/actions.inc.php
@@ -94,6 +94,8 @@ function condition_type2str($conditionType) {
 			return _('Event type');
 		case CONDITION_TYPE_HOST_METADATA:
 			return _('Host metadata');
+		case CONDITION_TYPE_SERVICE:
+			return _('IT service');
 		default:
 			return _('Unknown');
 	}
@@ -261,6 +263,21 @@ function condition_value2str($conditiontype, $value) {
 		case CONDITION_TYPE_EVENT_TYPE:
 			$str_val = eventType($value);
 			break;
+		case CONDITION_TYPE_SERVICE:
+			$srvs = API::Service()->get(array(
+				'serviceids' => $value,
+				'output' => array('name'),
+				'limit' => 1
+			));
+
+			if ($srvs) {
+				$srv = reset($srvs);
+				$str_val = $srv['name'];
+			}
+			else {
+				return _('Unknown');
+			}
+			break;
 		default:
 			return _('Unknown');
 	}
@@ -570,6 +587,11 @@ function get_conditions_by_eventsource($eventsource) {
 		CONDITION_TYPE_TEMPLATE,
 		CONDITION_TYPE_HOST
 	);
+	$conditions[EVENT_SOURCE_SERVICES] = array(
+		CONDITION_TYPE_SERVICE,
+		CONDITION_TYPE_TIME_PERIOD,
+		CONDITION_TYPE_MAINTENANCE
+	);
 
 	if (ZBX_DISTRIBUTED) {
 		array_push($conditions[EVENT_SOURCE_TRIGGERS], CONDITION_TYPE_NODE);
@@ -622,6 +644,10 @@ function get_operations_by_eventsource($eventsource) {
 	$operations[EVENT_SOURCE_INTERNAL] = array(
 		OPERATION_TYPE_MESSAGE
 	);
+	$operations[EVENT_SOURCE_SERVICES] = array(
+		OPERATION_TYPE_MESSAGE,
+		OPERATION_TYPE_COMMAND
+	);
 
 	if (isset($operations[$eventsource])) {
 		return $operations[$eventsource];
@@ -785,6 +811,10 @@ function get_operators_by_conditiontype($conditiontype) {
 		CONDITION_OPERATOR_LIKE,
 		CONDITION_OPERATOR_NOT_LIKE
 	);
+	$operators[CONDITION_TYPE_SERVICE] = array(
+		CONDITION_OPERATOR_EQUAL,
+		CONDITION_OPERATOR_NOT_EQUAL
+	);
 
 	if (isset($operators[$conditiontype])) {
 		return $operators[$conditiontype];
diff --git a/frontends/php/include/defines.inc.php b/frontends/php/include/defines.inc.php
index 36d9a8f..f84258b 100644
--- a/frontends/php/include/defines.inc.php
+++ b/frontends/php/include/defines.inc.php
@@ -20,7 +20,7 @@
 
 define('ZABBIX_VERSION',     '2.2.2');
 define('ZABBIX_API_VERSION', '2.2.2');
-define('ZABBIX_DB_VERSION',	 2020000);
+define('ZABBIX_DB_VERSION',	 2020003);
 
 define('ZABBIX_COPYRIGHT_FROM', '2001');
 define('ZABBIX_COPYRIGHT_TO',   '2014');
@@ -208,6 +208,7 @@ define('CONDITION_TYPE_DOBJECT',			21);
 define('CONDITION_TYPE_HOST_NAME',			22);
 define('CONDITION_TYPE_EVENT_TYPE',			23);
 define('CONDITION_TYPE_HOST_METADATA',		24);
+define('CONDITION_TYPE_SERVICE',		25);
 
 define('CONDITION_OPERATOR_EQUAL',		0);
 define('CONDITION_OPERATOR_NOT_EQUAL',	1);
@@ -453,6 +454,7 @@ define('EZ_TEXTING_LIMIT_CANADA',	1);
 define('ACTION_DEFAULT_SUBJ_TRIGGER', '{TRIGGER.STATUS}: {TRIGGER.NAME}');
 define('ACTION_DEFAULT_SUBJ_AUTOREG', 'Auto registration: {HOST.HOST}');
 define('ACTION_DEFAULT_SUBJ_DISCOVERY', 'Discovery: {DISCOVERY.DEVICE.STATUS} {DISCOVERY.DEVICE.IPADDRESS}');
+define('ACTION_DEFAULT_SUBJ_SERVICE', '{SERVICE.STATUS}: {SERVICE.NAME}');
 
 define('ACTION_DEFAULT_MSG_TRIGGER', "Trigger: {TRIGGER.NAME}\nTrigger status: {TRIGGER.STATUS}\n".
 		"Trigger severity: {TRIGGER.SEVERITY}\nTrigger URL: {TRIGGER.URL}\n\nItem values:\n\n".
@@ -468,6 +470,7 @@ define('ACTION_DEFAULT_MSG_DISCOVERY', "Discovery rule: {DISCOVERY.RULE.NAME}\n\
 		"Device service port: {DISCOVERY.SERVICE.PORT}\nDevice service status: {DISCOVERY.SERVICE.STATUS}\n".
 		"Device service uptime: {DISCOVERY.SERVICE.UPTIME}"
 );
+define('ACTION_DEFAULT_MSG_SERVICE', "Service: {SERVICE.NAME}\nService status: {SERVICE.STATUS}\n");
 
 define('ACTION_STATUS_ENABLED',		0);
 define('ACTION_STATUS_DISABLED',	1);
@@ -647,6 +650,7 @@ define('EVENT_SOURCE_TRIGGERS',				0);
 define('EVENT_SOURCE_DISCOVERY',			1);
 define('EVENT_SOURCE_AUTO_REGISTRATION',	2);
 define('EVENT_SOURCE_INTERNAL', 			3);
+define('EVENT_SOURCE_SERVICES',				4);
 
 define('EVENT_OBJECT_TRIGGER',			0);
 define('EVENT_OBJECT_DHOST',			1);
diff --git a/frontends/php/include/views/configuration.action.edit.php b/frontends/php/include/views/configuration.action.edit.php
index 158c07e..fd30151 100644
--- a/frontends/php/include/views/configuration.action.edit.php
+++ b/frontends/php/include/views/configuration.action.edit.php
@@ -47,7 +47,7 @@ $actionFormList->addRow(_('Name'), $nameTextBox);
 $actionFormList->addRow(_('Default subject'), new CTextBox('def_shortdata', $this->data['action']['def_shortdata'], ZBX_TEXTBOX_STANDARD_SIZE));
 $actionFormList->addRow(_('Default message'), new CTextArea('def_longdata', $this->data['action']['def_longdata']));
 
-if ($this->data['eventsource'] == EVENT_SOURCE_TRIGGERS || $this->data['eventsource'] == EVENT_SOURCE_INTERNAL) {
+if ($this->data['eventsource'] == EVENT_SOURCE_TRIGGERS || $this->data['eventsource'] == EVENT_SOURCE_INTERNAL || $this->data['eventsource'] == EVENT_SOURCE_SERVICES) {
 	$actionFormList->addRow(_('Recovery message'), new CCheckBox('recovery_msg', $this->data['action']['recovery_msg'], 'javascript: submit();', 1));
 	if ($this->data['action']['recovery_msg']) {
 		$actionFormList->addRow(_('Recovery subject'), new CTextBox('r_shortdata', $this->data['action']['r_shortdata'], ZBX_TEXTBOX_STANDARD_SIZE));
@@ -308,6 +308,17 @@ switch ($this->data['new_condition']['conditiontype']) {
 		$condition = new CTextBox('new_condition[value]', '', ZBX_TEXTBOX_STANDARD_SIZE);
 		break;
 
+	case CONDITION_TYPE_SERVICE:
+		$condition = new CMultiSelect(array(
+			'name' => 'new_condition[value][]',
+			'objectName' => 'services',
+			'objectOptions' => array(
+				'editable' => true
+			),
+			'defaultValue' => 0
+		));
+		break;
+
 	default:
 		$condition = null;
 }
@@ -323,7 +334,7 @@ $conditionFormList->addRow(_('New condition'), new CDiv($conditionTable, 'object
  */
 $operationFormList = new CFormList('operationlist');
 
-if ($this->data['eventsource'] == EVENT_SOURCE_TRIGGERS || $this->data['eventsource'] == EVENT_SOURCE_INTERNAL) {
+if ($this->data['eventsource'] == EVENT_SOURCE_TRIGGERS || $this->data['eventsource'] == EVENT_SOURCE_INTERNAL || $this->data['eventsource'] == EVENT_SOURCE_SERVICES) {
 	$operationFormList->addRow(_('Default operation step duration'), array(
 		new CNumericBox('esc_period', $this->data['action']['esc_period'], 6, 'no'),
 		' ('._('minimum 60 seconds').')')
@@ -333,7 +344,7 @@ if ($this->data['eventsource'] == EVENT_SOURCE_TRIGGERS || $this->data['eventsou
 // create operation table
 $operationsTable = new CTable(_('No operations defined.'), 'formElementTable');
 $operationsTable->attr('style', 'min-width: 600px;');
-if ($this->data['eventsource'] == EVENT_SOURCE_TRIGGERS || $this->data['eventsource'] == EVENT_SOURCE_INTERNAL) {
+if ($this->data['eventsource'] == EVENT_SOURCE_TRIGGERS || $this->data['eventsource'] == EVENT_SOURCE_INTERNAL || $this->data['eventsource'] == EVENT_SOURCE_SERVICES) {
 	$operationsTable->setHeader(array(_('Steps'), _('Details'), _('Start in'), _('Duration (sec)'), _('Action')));
 	$delay = count_operations_delay($this->data['action']['operations'], $this->data['action']['esc_period']);
 }
@@ -355,7 +366,7 @@ foreach ($this->data['action']['operations'] as $operationid => $operation) {
 	$details = new CSpan(get_operation_descr(SHORT_DESCRIPTION, $operation));
 	$details->setHint(get_operation_descr(LONG_DESCRIPTION, $operation));
 
-	if ($this->data['eventsource'] == EVENT_SOURCE_TRIGGERS || $this->data['eventsource'] == EVENT_SOURCE_INTERNAL) {
+	if ($this->data['eventsource'] == EVENT_SOURCE_TRIGGERS || $this->data['eventsource'] == EVENT_SOURCE_INTERNAL || $this->data['eventsource'] == EVENT_SOURCE_SERVICES) {
 		$esc_steps_txt = null;
 		$esc_period_txt = null;
 		$esc_delay_txt = null;
@@ -431,7 +442,7 @@ if (!empty($this->data['new_operation'])) {
 		$newOperationsTable->addItem(new CVar('new_operation[operationid]', $this->data['new_operation']['operationid']));
 	}
 
-	if ($this->data['eventsource'] == EVENT_SOURCE_TRIGGERS || $this->data['eventsource'] == EVENT_SOURCE_INTERNAL) {
+	if ($this->data['eventsource'] == EVENT_SOURCE_TRIGGERS || $this->data['eventsource'] == EVENT_SOURCE_INTERNAL || $this->data['eventsource'] == EVENT_SOURCE_SERVICES) {
 		$stepFrom = new CNumericBox('new_operation[esc_step_from]', $this->data['new_operation']['esc_step_from'], 5);
 		$stepFrom->attr('size', 6);
 		$stepFrom->addAction(
@@ -505,6 +516,10 @@ if (!empty($this->data['new_operation'])) {
 					$this->data['new_operation']['opmessage']['subject'] = ACTION_DEFAULT_SUBJ_AUTOREG;
 					$this->data['new_operation']['opmessage']['message'] = ACTION_DEFAULT_MSG_AUTOREG;
 				}
+				elseif ($this->data['eventsource'] == EVENT_SOURCE_SERVICES) {
+					$this->data['new_operation']['opmessage']['subject'] = ACTION_DEFAULT_SUBJ_SERVICE;
+					$this->data['new_operation']['opmessage']['message'] = ACTION_DEFAULT_MSG_SERVICE;
+				}
 				else {
 					$this->data['new_operation']['opmessage']['subject'] = '';
 					$this->data['new_operation']['opmessage']['message'] = '';
diff --git a/frontends/php/include/views/configuration.action.list.php b/frontends/php/include/views/configuration.action.list.php
index 4ce5559..7c95d41 100644
--- a/frontends/php/include/views/configuration.action.list.php
+++ b/frontends/php/include/views/configuration.action.list.php
@@ -34,6 +34,7 @@ $sourceComboBox->addItem(EVENT_SOURCE_TRIGGERS, _('Triggers'));
 $sourceComboBox->addItem(EVENT_SOURCE_DISCOVERY, _('Discovery'));
 $sourceComboBox->addItem(EVENT_SOURCE_AUTO_REGISTRATION, _('Auto registration'));
 $sourceComboBox->addItem(EVENT_SOURCE_INTERNAL, _x('Internal', 'event source'));
+$sourceComboBox->addItem(EVENT_SOURCE_SERVICES, _('IT services'));
 $filterForm = new CForm('get');
 $filterForm->addItem(array(_('Event source'), SPACE, $sourceComboBox));
 
diff --git a/frontends/php/jsrpc.php b/frontends/php/jsrpc.php
index e37e4cf..cf44fb7 100644
--- a/frontends/php/jsrpc.php
+++ b/frontends/php/jsrpc.php
@@ -413,6 +413,34 @@ switch ($data['method']) {
 					}
 				}
 				break;
+
+			case 'services':
+				$services = API::Service()->get(array(
+					'editable' => isset($data['editable']) ? $data['editable'] : null,
+					'output' => array('name'),
+					'search' => isset($data['search']) ? array('name' => $data['search']) : null,
+					'filter' => isset($data['filter']) ? $data['filter'] : null,
+					'limit' => isset($data['limit']) ? $data['limit'] : null
+				));
+
+				if ($services) {
+
+					$sortFields[] = array('field' => 'name', 'order' => ZBX_SORT_UP);
+					CArrayHelper::sort($services, $sortFields);
+
+					if (isset($data['limit'])) {
+						$services = array_slice($services, 0, $data['limit']);
+					}
+
+					foreach ($services as $service) {
+						$result[] = array(
+							'id' => $service['serviceid'],
+							'prefix' => '',
+							'name' => $service['name']
+						);
+					}
+				}
+				break;
 		}
 		break;
 
diff --git a/frontends/php/locale/en_US/LC_MESSAGES/frontend.po b/frontends/php/locale/en_US/LC_MESSAGES/frontend.po
index f7c6985..29897bc 100644
--- a/frontends/php/locale/en_US/LC_MESSAGES/frontend.po
+++ b/frontends/php/locale/en_US/LC_MESSAGES/frontend.po
@@ -6877,7 +6877,11 @@ msgstr "Incorrect action condition port \"%1$s\"."
 msgid "Incorrect action condition proxy. Proxy does not exist or you have no access to it."
 msgstr "Incorrect action condition proxy. Proxy does not exist or you have no access to it."
 
-#: api/classes/CAction.php:1696
+#: api/classes/CAction.php:1719
+msgid "Incorrect action condition service. IT service does not exist or you have no access to it."
+msgstr "Incorrect action condition service. IT service does not exist or you have no access to it."
+
+#: api/classes/CAction.php:1704
 msgid "Incorrect action condition template. Template does not exist or you have no access to it."
 msgstr "Incorrect action condition template. Template does not exist or you have no access to it."
 
diff --git a/frontends/php/locale/ru/LC_MESSAGES/frontend.po b/frontends/php/locale/ru/LC_MESSAGES/frontend.po
index 7f9f342..683ac8c 100644
--- a/frontends/php/locale/ru/LC_MESSAGES/frontend.po
+++ b/frontends/php/locale/ru/LC_MESSAGES/frontend.po
@@ -6922,7 +6922,11 @@ msgstr "Некорректный порт \"%1$s\" в условии дейст
 msgid "Incorrect action condition proxy. Proxy does not exist or you have no access to it."
 msgstr "Некорректный прокси в условии действия. Прокси не существует или у вас нет прав доступа к нему."
 
-#: api/classes/CAction.php:1696
+#: api/classes/CAction.php:1719
+msgid "Incorrect action condition service. IT service does not exist or you have no access to it."
+msgstr "Некорректная услуга в условии действия. IT-услуга не существует или у вас нет прав доступа к ней."
+
+#: api/classes/CAction.php:1704
 msgid "Incorrect action condition template. Template does not exist or you have no access to it."
 msgstr "Некорректный шаблон в условии действия. Шаблон не существует или у вас нет прав доступа к нему."
 
diff --git a/include/common.h b/include/common.h
index 2b5c247..070327a 100644
--- a/include/common.h
+++ b/include/common.h
@@ -160,6 +160,7 @@ zbx_item_authtype_t;
 #define EVENT_SOURCE_DISCOVERY		1
 #define EVENT_SOURCE_AUTO_REGISTRATION	2
 #define EVENT_SOURCE_INTERNAL		3
+#define EVENT_SOURCE_SERVICES		4
 
 /* event objects */
 #define EVENT_OBJECT_TRIGGER		0
@@ -168,6 +169,7 @@ zbx_item_authtype_t;
 #define EVENT_OBJECT_ZABBIX_ACTIVE	3
 #define EVENT_OBJECT_ITEM		4
 #define EVENT_OBJECT_LLDRULE		5
+#define EVENT_OBJECT_SERVICE		6
 
 /* acknowledged flags */
 #define EVENT_NOT_ACKNOWLEDGED		0
@@ -303,6 +305,7 @@ const char	*zbx_dservice_type_string(zbx_dservice_type_t service);
 #define CONDITION_TYPE_HOST_NAME		22
 #define CONDITION_TYPE_EVENT_TYPE		23
 #define CONDITION_TYPE_HOST_METADATA		24
+#define CONDITION_TYPE_SERVICE			25
 
 /* condition operators */
 #define CONDITION_OPERATOR_EQUAL		0
diff --git a/include/db.h b/include/db.h
index 8dee42b..339b68c 100644
--- a/include/db.h
+++ b/include/db.h
@@ -288,6 +288,7 @@ typedef struct
 	int		value;
 	int		acknowledged;
 	int		ns;
+	zbx_uint64_t	parentid;
 }
 DB_EVENT;
 
@@ -408,6 +409,7 @@ typedef struct
 	zbx_uint64_t		actionid;
 	zbx_uint64_t		triggerid;
 	zbx_uint64_t		itemid;
+	zbx_uint64_t		serviceid;
 	zbx_uint64_t		eventid;
 	zbx_uint64_t		r_eventid;
 	int			nextcheck;
@@ -521,7 +523,7 @@ void	DBdelete_triggers(zbx_vector_uint64_t *triggerids);
 void	DBdelete_graphs(zbx_vector_uint64_t *graphids);
 void	DBdelete_hosts(zbx_vector_uint64_t *hostids);
 void	DBget_graphitems(const char *sql, ZBX_GRAPH_ITEMS **gitems, size_t *gitems_alloc, size_t *gitems_num);
-void	DBupdate_services(zbx_uint64_t triggerid, int status, int clock);
+void	DBupdate_services(zbx_uint64_t eventid, zbx_uint64_t triggerid, int status, int clock);
 
 void	DBadd_trend(zbx_uint64_t itemid, double value, int clock);
 void	DBadd_trend_uint(zbx_uint64_t itemid, zbx_uint64_t value, int clock);
diff --git a/src/libs/zbxdbhigh/host.c b/src/libs/zbxdbhigh/host.c
index 8583635..b3e078e 100644
--- a/src/libs/zbxdbhigh/host.c
+++ b/src/libs/zbxdbhigh/host.c
@@ -24,6 +24,7 @@
 #include "dbcache.h"
 #include "zbxserver.h"
 #include "mutexs.h"
+#include "events.h"
 
 #define LOCK_SERVICES	zbx_mutex_lock(&services_lock)
 #define UNLOCK_SERVICES	zbx_mutex_unlock(&services_lock)
@@ -913,9 +914,10 @@ static int	latest_service_alarm(zbx_uint64_t serviceid, int status)
 	return ret;
 }
 
-static void	DBadd_service_alarm(zbx_uint64_t serviceid, int status, int clock)
+static void	DBadd_service_alarm(zbx_uint64_t serviceid, int status, int clock, zbx_uint64_t eventid)
 {
 	const char	*__function_name = "DBadd_service_alarm";
+        zbx_timespec_t ts;
 
 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
 
@@ -924,6 +926,9 @@ static void	DBadd_service_alarm(zbx_uint64_t serviceid, int status, int clock)
 		DBexecute("insert into service_alarms (servicealarmid,serviceid,clock,value)"
 			" values(" ZBX_FS_UI64 "," ZBX_FS_UI64 ",%d,%d)",
 			DBget_maxid("service_alarms"), serviceid, clock, status);
+                ts.sec = clock;
+                ts.ns = 0;
+                add_child_event(eventid, 0, EVENT_SOURCE_SERVICES, EVENT_OBJECT_SERVICE, serviceid, &ts, status);
 	}
 
 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);
@@ -943,7 +948,7 @@ static void	DBadd_service_alarm(zbx_uint64_t serviceid, int status, int clock)
  *           !!! Don't forget to sync the code with PHP !!!                   *
  *                                                                            *
  ******************************************************************************/
-static void	DBupdate_services_rec(zbx_uint64_t serviceid, int clock)
+static void	DBupdate_services_rec(zbx_uint64_t serviceid, int clock, zbx_uint64_t eventid)
 {
 	int		algorithm, status = 0;
 	zbx_uint64_t	serviceupid;
@@ -965,7 +970,7 @@ static void	DBupdate_services_rec(zbx_uint64_t serviceid, int clock)
 		{
 			status = DBget_service_status(serviceupid, algorithm, 0);
 
-			DBadd_service_alarm(serviceupid, status, clock);
+			DBadd_service_alarm(serviceupid, status, clock, eventid);
 			DBexecute("update services set status=%d where serviceid=" ZBX_FS_UI64, status, serviceupid);
 		}
 		else if (SERVICE_ALGORITHM_NONE != algorithm)
@@ -981,7 +986,7 @@ static void	DBupdate_services_rec(zbx_uint64_t serviceid, int clock)
 	while (NULL != (row = DBfetch(result)))
 	{
 		ZBX_STR2UINT64(serviceupid, row[0]);
-		DBupdate_services_rec(serviceupid, clock);
+		DBupdate_services_rec(serviceupid, clock, eventid);
 	}
 	DBfree_result(result);
 }
@@ -1029,7 +1034,7 @@ static void	DBupdate_services_status_all()
 				" where serviceid=" ZBX_FS_UI64,
 				status, serviceid);
 
-		DBadd_service_alarm(serviceid, status, clock);
+		DBadd_service_alarm(serviceid, status, clock, 0);
 	}
 	DBfree_result(result);
 
@@ -1042,7 +1047,7 @@ static void	DBupdate_services_status_all()
 	while (NULL != (row = DBfetch(result)))
 	{
 		ZBX_STR2UINT64(serviceid, row[0]);
-		DBupdate_services_rec(serviceid, clock);
+		DBupdate_services_rec(serviceid, clock, 0);
 	}
 	DBfree_result(result);
 }
@@ -1061,7 +1066,7 @@ static void	DBupdate_services_status_all()
  * Comments: !!! Don't forget to sync the code with PHP !!!                   *
  *                                                                            *
  ******************************************************************************/
-void	DBupdate_services(zbx_uint64_t triggerid, int status, int clock)
+void	DBupdate_services(zbx_uint64_t eventid, zbx_uint64_t triggerid, int status, int clock)
 {
 	DB_RESULT	result;
 	DB_ROW		row;
@@ -1077,8 +1082,8 @@ void	DBupdate_services(zbx_uint64_t triggerid, int status, int clock)
 
 		DBexecute("update services set status=%d where serviceid=" ZBX_FS_UI64, status, serviceid);
 
-		DBadd_service_alarm(serviceid, status, clock);
-		DBupdate_services_rec(serviceid, clock);
+		DBadd_service_alarm(serviceid, status, clock, eventid);
+		DBupdate_services_rec(serviceid, clock, eventid);
 	}
 
 	UNLOCK_SERVICES;
diff --git a/src/libs/zbxdbupgrade/dbupgrade.c b/src/libs/zbxdbupgrade/dbupgrade.c
index 8064c15..7434b7c 100644
--- a/src/libs/zbxdbupgrade/dbupgrade.c
+++ b/src/libs/zbxdbupgrade/dbupgrade.c
@@ -2396,6 +2396,32 @@ static int	DBpatch_2020000(void)
 	return SUCCEED;
 }
 
+static int	DBpatch_2020002(void)
+{
+	const ZBX_FIELD	field = {"serviceid", NULL, "services", "serviceid", 0, ZBX_TYPE_ID, 0, 0};
+        int ret;
+
+        ret = DBadd_field("escalations", &field);
+
+        if (ret == SUCCEED)
+          DBadd_foreign_key("escalations", 1, &field);
+
+        return ret;
+}
+
+static int	DBpatch_2020003(void)
+{
+	const ZBX_FIELD	field = {"parentid", NULL, "events", "eventid", 0, ZBX_TYPE_ID, 0, 0};
+        int ret;
+
+        ret = DBadd_field("events", &field);
+
+        if (ret == SUCCEED)
+          DBadd_foreign_key("events", 1, &field);
+
+        return ret;
+}
+
 #define DBPATCH_START()					zbx_dbpatch_t	patches[] = {
 #define DBPATCH_ADD(version, duplicates, mandatory)	{DBpatch_##version, version, duplicates, mandatory},
 #define DBPATCH_END()					{NULL}};
@@ -2644,6 +2670,8 @@ int	DBcheck_version(void)
 	DBPATCH_ADD(2010199, 0, 1)
 	DBPATCH_ADD(2020000, 0, 1)
 	/* Patch 2020001 is reserved for ZBXNEXT-2124 */
+	DBPATCH_ADD(2020002, 0, 1)
+	DBPATCH_ADD(2020003, 0, 1)
 
 	DBPATCH_END()
 
diff --git a/src/libs/zbxserver/expression.c b/src/libs/zbxserver/expression.c
index b5e613e..0d1fc8d 100644
--- a/src/libs/zbxserver/expression.c
+++ b/src/libs/zbxserver/expression.c
@@ -2030,6 +2030,11 @@ static int	get_autoreg_value_by_event(const DB_EVENT *event, char **replace_to,
 #define MVAR_DISCOVERY_DEVICE_STATUS	"{DISCOVERY.DEVICE.STATUS}"
 #define MVAR_DISCOVERY_DEVICE_UPTIME	"{DISCOVERY.DEVICE.UPTIME}"
 
+/* IT Services */
+#define MVAR_SERVICE_ID			"{SERVICE.ID}"
+#define MVAR_SERVICE_NAME		"{SERVICE.NAME}"
+#define MVAR_SERVICE_STATUS		"{SERVICE.STATUS}"
+
 #define STR_UNKNOWN_VARIABLE		"*UNKNOWN*"
 
 static const char	*ex_macros[] =
@@ -2483,6 +2488,57 @@ static void	cache_trigger_hostids(zbx_vector_uint64_t *hostids, const char *expr
 
 /******************************************************************************
  *                                                                            *
+ * Function: DBget_service_name                                               *
+ *                                                                            *
+ * Purpose: get the name of the given IT service                              *
+ *                                                                            *
+ * Return value: upon successful completion return SUCCEED                    *
+ *               otherwise FAIL                                               *
+ *                                                                            *
+ ******************************************************************************/
+static int	DBget_service_name(zbx_uint64_t serviceid, char **replace_to)
+{
+	const char	*__function_name = "DBget_service_name";
+
+	DB_RESULT	result;
+	DB_ROW		row;
+	int		ret = FAIL;
+	char		*sql = NULL;
+	size_t		replace_to_alloc = 64, replace_to_offset = 0,
+			sql_alloc = 256, sql_offset = 0;
+
+	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
+
+	*replace_to = zbx_realloc(*replace_to, replace_to_alloc);
+	**replace_to = '\0';
+
+	sql = zbx_malloc(sql, sql_alloc);
+
+	zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset,
+			"select name"
+			" from services"
+			" where serviceid=" ZBX_FS_UI64,
+			serviceid);
+	zbx_strcpy_alloc(&sql, &sql_alloc, &sql_offset, " order by name");
+
+	result = DBselect("%s", sql);
+
+	zbx_free(sql);
+
+	if (NULL != (row = DBfetch(result)))
+	{
+		zbx_strcpy_alloc(replace_to, &replace_to_alloc, &replace_to_offset, row[0]);
+		ret = SUCCEED;
+	}
+	DBfree_result(result);
+out:
+	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(ret));
+
+	return ret;
+}
+
+/******************************************************************************
+ *                                                                            *
  * Function: substitute_simple_macros                                         *
  *                                                                            *
  * Purpose: substitute simple macros in data string with real values          *
@@ -3321,6 +3377,25 @@ int	substitute_simple_macros(zbx_uint64_t *actionid, const DB_EVENT *event, DB_E
 					replace_to = zbx_strdup(replace_to, zbx_time2str(time(NULL)));
 				}
 			}
+                        else if (EVENT_SOURCE_SERVICES == c_event->source)
+			{
+                          if (0 == strcmp(m, MVAR_SERVICE_ID))
+                          {
+                            replace_to = zbx_dsprintf(replace_to, ZBX_FS_UI64, c_event->objectid);
+                          }
+                          else if (0 == strcmp(m, MVAR_SERVICE_NAME))
+                          {
+                            ret = DBget_service_name(c_event->objectid, &replace_to);
+                          }
+                          else if (0 == strcmp(m, MVAR_SERVICE_STATUS) || 0 == strcmp(m, MVAR_STATUS))
+                          {
+                            if (c_event->value) {
+                              DCget_trigger_severity_name(c_event->value, &replace_to);
+                            } else {
+                              replace_to = zbx_strdup(replace_to, "OK");
+                            }
+                          }
+                        }
 		}
 		else if (0 != (macro_type & (MACRO_TYPE_TRIGGER_DESCRIPTION | MACRO_TYPE_TRIGGER_COMMENTS)))
 		{
diff --git a/src/zabbix_proxy/events.c b/src/zabbix_proxy/events.c
index 89c2096..2537867 100644
--- a/src/zabbix_proxy/events.c
+++ b/src/zabbix_proxy/events.c
@@ -25,6 +25,11 @@ void	add_event(zbx_uint64_t eventid, unsigned char source, unsigned char object,
 {
 }
 
+void	add_child_event(zbx_uint64_t parentid, zbx_uint64_t eventid, unsigned char source, unsigned char object,
+                        zbx_uint64_t objectid, const zbx_timespec_t *timespec, int value)
+{
+}
+
 int	process_events(void)
 {
 	return 0;
diff --git a/src/zabbix_server/actions.c b/src/zabbix_server/actions.c
index 3f6a506..91c573a 100644
--- a/src/zabbix_server/actions.c
+++ b/src/zabbix_server/actions.c
@@ -1269,6 +1269,68 @@ out:
 
 /******************************************************************************
  *                                                                            *
+ * Function: check_service_condition                                          *
+ *                                                                            *
+ * Purpose: check if event matches single condition                           *
+ *                                                                            *
+ * Parameters: event - service event to check                                 *
+ *                                  (event->source == EVENT_SOURCE_SERVICES)  *
+ *             condition - condition for matching                             *
+ *                                                                            *
+ * Return value: SUCCEED - matches, FAIL - otherwise                          *
+ *                                                                            *
+ * Author: Paul Wolneykien                                                    *
+ *                                                                            *
+ ******************************************************************************/
+static int	check_service_condition(const DB_EVENT *event, DB_CONDITION *condition)
+{
+	const char	*__function_name = "check_service_condition";
+	zbx_uint64_t	condition_value;
+	int		ret = FAIL;
+
+	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
+
+
+	if (CONDITION_TYPE_SERVICE == condition->conditiontype)
+	{
+		ZBX_STR2UINT64(condition_value, condition->value);
+
+		switch (condition->operator)
+		{
+			case CONDITION_OPERATOR_EQUAL:
+			case CONDITION_OPERATOR_NOT_EQUAL:
+				if (event->objectid == condition_value)
+				{
+					ret = SUCCEED;
+				}
+
+				if (CONDITION_OPERATOR_NOT_EQUAL == condition->operator)
+					ret = (SUCCEED == ret) ? FAIL : SUCCEED;
+				break;
+			default:
+				ret = NOTSUPPORTED;
+		}
+	}
+	else
+	{
+		zabbix_log(LOG_LEVEL_ERR, "unsupported condition type [%d] for condition id [" ZBX_FS_UI64 "]",
+				(int)condition->conditiontype, condition->conditionid);
+	}
+
+	if (NOTSUPPORTED == ret)
+	{
+		zabbix_log(LOG_LEVEL_ERR, "unsupported operator [%d] for condition id [" ZBX_FS_UI64 "]",
+				(int)condition->operator, condition->conditionid);
+		ret = FAIL;
+	}
+
+	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(ret));
+
+	return ret;
+}
+
+/******************************************************************************
+ *                                                                            *
  * Function: check_action_condition                                           *
  *                                                                            *
  * Purpose: check if event matches single condition                           *
@@ -1303,6 +1365,9 @@ int	check_action_condition(const DB_EVENT *event, DB_CONDITION *condition)
 		case EVENT_SOURCE_INTERNAL:
 			ret = check_internal_condition(event, condition);
 			break;
+		case EVENT_SOURCE_SERVICES:
+			ret = check_service_condition(event, condition);
+			break;
 		default:
 			zabbix_log(LOG_LEVEL_ERR, "unsupported event source [%d] for condition id [" ZBX_FS_UI64 "]",
 					event->source, condition->conditionid);
@@ -1528,9 +1593,9 @@ static void	execute_operations(const DB_EVENT *event, zbx_uint64_t actionid)
 static void	get_escalation_sql(char **sql, size_t *sql_alloc, size_t *sql_offset,
 		zbx_uint64_t actionid, const DB_EVENT *event, unsigned char recovery)
 {
-	zbx_uint64_t	escalationid, triggerid = 0, itemid = 0, eventid = 0, r_eventid = 0;
+	zbx_uint64_t	escalationid, triggerid = 0, itemid = 0, serviceid = 0, eventid = 0, r_eventid = 0;
 	const char	*ins_escalation_sql =
-			"insert into escalations (escalationid,actionid,status,triggerid,itemid,eventid,r_eventid)"
+			"insert into escalations (escalationid,actionid,status,triggerid,itemid,serviceid,eventid,r_eventid)"
 			" values ";
 
 	escalationid = DBget_maxid("escalations");
@@ -1544,6 +1609,9 @@ static void	get_escalation_sql(char **sql, size_t *sql_alloc, size_t *sql_offset
 		case EVENT_OBJECT_LLDRULE:
 			itemid = event->objectid;
 			break;
+		case EVENT_OBJECT_SERVICE:
+			serviceid = event->objectid;
+			break;
 	}
 
 	if (0 == recovery)
@@ -1562,9 +1630,9 @@ static void	get_escalation_sql(char **sql, size_t *sql_alloc, size_t *sql_offset
 #ifndef HAVE_MULTIROW_INSERT
 	zbx_strcpy_alloc(sql, sql_alloc, sql_offset, ins_escalation_sql);
 #endif
-	zbx_snprintf_alloc(sql, sql_alloc, sql_offset, "(" ZBX_FS_UI64 "," ZBX_FS_UI64 ",%d,%s,%s,%s,%s)" ZBX_ROW_DL,
+	zbx_snprintf_alloc(sql, sql_alloc, sql_offset, "(" ZBX_FS_UI64 "," ZBX_FS_UI64 ",%d,%s,%s,%s,%s,%s)" ZBX_ROW_DL,
 			escalationid, actionid, ESCALATION_STATUS_ACTIVE, DBsql_id_ins(triggerid), DBsql_id_ins(itemid),
-			DBsql_id_ins(eventid), DBsql_id_ins(r_eventid));
+			DBsql_id_ins(serviceid), DBsql_id_ins(eventid), DBsql_id_ins(r_eventid));
 }
 
 /******************************************************************************
@@ -1622,7 +1690,8 @@ void	process_actions(const DB_EVENT *events, size_t events_num)
 					execute_operations(event, actionid);
 				}
 			}
-			else if (EVENT_SOURCE_TRIGGERS == event->source || EVENT_SOURCE_INTERNAL == event->source)
+			else if (EVENT_SOURCE_TRIGGERS == event->source || EVENT_SOURCE_INTERNAL == event->source ||
+                                 EVENT_SOURCE_SERVICES == event->source)
 			{
 				/* Action conditions evaluated to false, but it could be a recovery */
 				/* event for this action. Remember this and check escalations later. */
@@ -1644,14 +1713,14 @@ void	process_actions(const DB_EVENT *events, size_t events_num)
 	{
 		char		*sql2 = NULL;
 		size_t		sql2_alloc = 0, sql2_offset = 0;
-		zbx_uint64_t	triggerid, itemid;
+		zbx_uint64_t	triggerid, itemid, serviceid;
 
 		zbx_vector_uint64_sort(&rec_actionids, ZBX_DEFAULT_UINT64_COMPARE_FUNC);
 		zbx_vector_uint64_uniq(&rec_actionids, ZBX_DEFAULT_UINT64_COMPARE_FUNC);
 
 		/* list of ongoing escalations matching actionids collected before */
 		zbx_strcpy_alloc(&sql2, &sql2_alloc, &sql2_offset,
-				"select actionid,triggerid,itemid"
+				"select actionid,triggerid,itemid,serviceid"
 				" from escalations"
 				" where eventid is not null"
 					" and");
@@ -1665,6 +1734,7 @@ void	process_actions(const DB_EVENT *events, size_t events_num)
 			ZBX_STR2UINT64(actionid, row[0]);
 			ZBX_DBROW2UINT64(triggerid, row[1]);
 			ZBX_DBROW2UINT64(itemid, row[2]);
+			ZBX_DBROW2UINT64(serviceid, row[3]);
 
 			for (i = 0; i < rec_mapping.values_num; i++)
 			{
@@ -1697,6 +1767,10 @@ void	process_actions(const DB_EVENT *events, size_t events_num)
 						}
 
 						break;
+					case EVENT_SOURCE_SERVICES:
+						if (serviceid != event->objectid)
+							continue;
+						break;
 					default:
 						continue;
 				}
diff --git a/src/zabbix_server/escalator/escalator.c b/src/zabbix_server/escalator/escalator.c
index e78fe7f..654d99d 100644
--- a/src/zabbix_server/escalator/escalator.c
+++ b/src/zabbix_server/escalator/escalator.c
@@ -218,6 +218,51 @@ static int	get_item_permission(zbx_uint64_t userid, zbx_uint64_t itemid)
 	return perm;
 }
 
+/******************************************************************************
+ *                                                                            *
+ * Function: get_service_permission                                           *
+ *                                                                            *
+ * Purpose: Return user permissions for access to the given service           *
+ *                                                                            *
+ * Return value: PERM_DENY - if the specified trigger or user not found,      *
+ *                   or permission otherwise                                  *
+ *                                                                            *
+ ******************************************************************************/
+static int	get_service_permission(zbx_uint64_t userid, zbx_uint64_t serviceid)
+{
+	const char	*__function_name = "get_service_permission";
+	DB_RESULT	result;
+	DB_ROW		row;
+	int		perm = PERM_READ, trigger_perm;
+	zbx_uint64_t	triggerid;
+
+	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
+
+	result = DBselect(
+			"select triggerid"
+			" from services"
+			" where serviceid=" ZBX_FS_UI64,
+			serviceid);
+
+	while (NULL != (row = DBfetch(result)))
+	{
+          if (row[0]) {
+		ZBX_STR2UINT64(triggerid, row[0]);
+                if (triggerid) {
+                  perm = PERM_DENY;
+                  trigger_perm = get_trigger_permission(userid, triggerid);
+                  if (perm < trigger_perm)
+                    perm = trigger_perm;
+                }
+          }
+	}
+	DBfree_result(result);
+
+	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_permission_string(perm));
+
+	return perm;
+}
+
 static void	add_user_msg(zbx_uint64_t userid, zbx_uint64_t mediatypeid, ZBX_USER_MSG **user_msg,
 		const char *subject, const char *message)
 {
@@ -253,6 +298,9 @@ static void	add_user_msg(zbx_uint64_t userid, zbx_uint64_t mediatypeid, ZBX_USER
 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);
 }
 
+static int	get_event_info(zbx_uint64_t eventid, DB_EVENT *event);
+static void	free_event_info(DB_EVENT *event);
+
 static void	add_object_msg(zbx_uint64_t actionid, zbx_uint64_t operationid, zbx_uint64_t mediatypeid,
 		ZBX_USER_MSG **user_msg, const char *subject, const char *message, DB_EVENT *event)
 {
@@ -293,6 +341,10 @@ static void	add_object_msg(zbx_uint64_t actionid, zbx_uint64_t operationid, zbx_
 				if (PERM_READ > get_item_permission(userid, event->objectid))
 					continue;
 				break;
+			case EVENT_OBJECT_SERVICE:
+				if (PERM_READ > get_service_permission(userid, event->objectid))
+					continue;
+				break;
 		}
 
 		subject_dyn = zbx_strdup(NULL, subject);
@@ -303,6 +355,21 @@ static void	add_object_msg(zbx_uint64_t actionid, zbx_uint64_t operationid, zbx_
 		substitute_simple_macros(&actionid, event, NULL, &userid, NULL, NULL, NULL,
 				&message_dyn, MACRO_TYPE_MESSAGE_NORMAL, NULL, 0);
 
+                if (event->parentid) {
+                  zbx_uint64_t    parentid;
+                  DB_EVENT        parent;
+                  parentid = event->parentid;
+                  while (parentid) {
+                    get_event_info(parentid, &parent);
+                    substitute_simple_macros(&actionid, &parent, NULL, &userid, NULL, NULL, NULL,
+                                             &subject_dyn, MACRO_TYPE_MESSAGE_NORMAL, NULL, 0);
+                    substitute_simple_macros(&actionid, &parent, NULL, &userid, NULL, NULL, NULL,
+                                             &message_dyn, MACRO_TYPE_MESSAGE_NORMAL, NULL, 0);
+                    parentid = parent.parentid;
+                    free_event_info(&parent);
+                  }
+                }
+
 		add_user_msg(userid, mediatypeid, user_msg, subject_dyn, message_dyn);
 
 		zbx_free(subject_dyn);
@@ -772,6 +839,18 @@ static void	execute_commands(DB_EVENT *event, zbx_uint64_t actionid, zbx_uint64_
 			script.command = zbx_strdup(script.command, row[11]);
 			substitute_simple_macros(&actionid, event, NULL, NULL, NULL, NULL, NULL,
 					&script.command, MACRO_TYPE_MESSAGE_NORMAL, NULL, 0);
+                        if (event->parentid) {
+                          zbx_uint64_t    parentid;
+                          DB_EVENT        parent;
+                          parentid = event->parentid;
+                          while (parentid) {
+                            get_event_info(parentid, &parent);
+                            substitute_simple_macros(&actionid, &parent, NULL, NULL, NULL, NULL, NULL,
+                                                     &script.command, MACRO_TYPE_MESSAGE_NORMAL, NULL, 0);
+                            parentid = parent.parentid;
+                            free_event_info(&parent);
+                          }
+                        }
 		}
 
 		if (SUCCEED == rc)
@@ -1202,6 +1281,36 @@ static void	process_recovery_msg(DB_ESCALATION *escalation, DB_EVENT *event, DB_
 			substitute_simple_macros(&action->actionid, event, r_event, &userid, NULL, NULL, NULL,
 					&message_dyn, MACRO_TYPE_MESSAGE_RECOVERY, NULL, 0);
 
+                        if (r_event->parentid) {
+                          zbx_uint64_t    parentid;
+                          DB_EVENT        parent;
+                          parentid = r_event->parentid;
+                          while (parentid) {
+                            get_event_info(parentid, &parent);
+                            substitute_simple_macros(&action->actionid, event, &parent, &userid, NULL, NULL, NULL,
+                                                     &subject_dyn, MACRO_TYPE_MESSAGE_RECOVERY, NULL, 0);
+                            substitute_simple_macros(&action->actionid, event, &parent, &userid, NULL, NULL, NULL,
+                                                 &message_dyn, MACRO_TYPE_MESSAGE_RECOVERY, NULL, 0);
+                            parentid = parent.parentid;
+                            free_event_info(&parent);
+                          }
+                        }
+
+                        if (event->parentid) {
+                          zbx_uint64_t    parentid;
+                          DB_EVENT        parent;
+                          parentid = event->parentid;
+                          while (parentid) {
+                            get_event_info(parentid, &parent);
+                            substitute_simple_macros(&action->actionid, &parent, r_event, &userid, NULL, NULL, NULL,
+                                                     &subject_dyn, MACRO_TYPE_MESSAGE_RECOVERY, NULL, 0);
+                            substitute_simple_macros(&action->actionid, &parent, r_event, &userid, NULL, NULL, NULL,
+                                                 &message_dyn, MACRO_TYPE_MESSAGE_RECOVERY, NULL, 0);
+                            parentid = parent.parentid;
+                            free_event_info(&parent);
+                          }
+                        }
+
 			escalation->esc_step = 0;
 
 			add_user_msg(userid, mediatypeid, &user_msg, subject_dyn, message_dyn);
@@ -1257,7 +1366,7 @@ static int	get_event_info(zbx_uint64_t eventid, DB_EVENT *event)
 
 	memset(event, 0, sizeof(DB_EVENT));
 
-	result = DBselect("select eventid,source,object,objectid,clock,value,acknowledged,ns"
+	result = DBselect("select eventid,source,object,objectid,clock,value,acknowledged,ns,parentid"
 			" from events"
 			" where eventid=" ZBX_FS_UI64,
 			eventid);
@@ -1272,6 +1381,9 @@ static int	get_event_info(zbx_uint64_t eventid, DB_EVENT *event)
 		event->value = atoi(row[5]);
 		event->acknowledged = atoi(row[6]);
 		event->ns = atoi(row[7]);
+                if (row[8]) {
+                  ZBX_STR2UINT64(event->parentid, row[8]);
+                }
 
 		res = SUCCEED;
 	}
@@ -1584,10 +1696,10 @@ static int	process_escalations(int now, int *nextcheck)
 	sql = zbx_malloc(sql, sql_alloc);
 
 	result = DBselect(
-			"select escalationid,actionid,triggerid,eventid,r_eventid,nextcheck,esc_step,status,itemid"
+			"select escalationid,actionid,triggerid,eventid,r_eventid,nextcheck,esc_step,status,itemid,serviceid"
 			" from escalations"
 			ZBX_SQL_NODE
-			" order by actionid,triggerid,itemid,escalationid",
+			" order by actionid,triggerid,itemid,serviceid,escalationid",
 			DBwhere_node_local("escalationid"));
 
 	*nextcheck = now + CONFIG_ESCALATOR_FREQUENCY;
@@ -1610,6 +1722,7 @@ static int	process_escalations(int now, int *nextcheck)
 			last_escalation.esc_step = atoi(row[6]);
 			last_escalation.status = atoi(row[7]);
 			ZBX_DBROW2UINT64(last_escalation.itemid, row[8]);
+			ZBX_DBROW2UINT64(last_escalation.serviceid, row[9]);
 
 			/* just delete on the next cycle */
 			if (0 != last_escalation.r_eventid)
@@ -1630,7 +1743,8 @@ static int	process_escalations(int now, int *nextcheck)
 		{
 			esc_superseded = (escalation.actionid == last_escalation.actionid &&
 					escalation.triggerid == last_escalation.triggerid &&
-					escalation.itemid == last_escalation.itemid);
+					escalation.itemid == last_escalation.itemid &&
+					escalation.serviceid == last_escalation.serviceid);
 
 			if (0 != esc_superseded)
 			{
diff --git a/src/zabbix_server/events.c b/src/zabbix_server/events.c
index 2b2ea52..91ee8d9 100644
--- a/src/zabbix_server/events.c
+++ b/src/zabbix_server/events.c
@@ -66,6 +66,7 @@ void	add_event(zbx_uint64_t eventid, unsigned char source, unsigned char object,
 	events[events_num].ns = timespec->ns;
 	events[events_num].value = value;
 	events[events_num].acknowledged = EVENT_NOT_ACKNOWLEDGED;
+        events[events_num].parentid = 0;
 
 	if (EVENT_SOURCE_TRIGGERS == source)
 	{
@@ -79,6 +80,13 @@ void	add_event(zbx_uint64_t eventid, unsigned char source, unsigned char object,
 	events_num++;
 }
 
+void	add_child_event(zbx_uint64_t parentid, zbx_uint64_t eventid, unsigned char source, unsigned char object,
+                        zbx_uint64_t objectid, const zbx_timespec_t *timespec, int value)
+{
+  add_event(eventid, source, object, objectid, timespec, value, NULL, NULL, 0, 0);
+  events[events_num - 1].parentid = parentid;
+}
+
 /******************************************************************************
  *                                                                            *
  * Function: save_events                                                      *
@@ -86,11 +94,11 @@ void	add_event(zbx_uint64_t eventid, unsigned char source, unsigned char object,
  * Purpose: flushes the events into a database                                *
  *                                                                            *
  ******************************************************************************/
-static void	save_events()
+static void	save_events(int from)
 {
 	char		*sql = NULL;
 	size_t		sql_alloc = 2 * ZBX_KIBIBYTE, sql_offset = 0, i;
-	const char	*ins_event_sql = "insert into events (eventid,source,object,objectid,clock,ns,value) values ";
+	const char	*ins_event_sql = "insert into events (eventid,source,object,objectid,clock,ns,value,parentid) values ";
 
 	sql = zbx_malloc(sql, sql_alloc);
 
@@ -100,7 +108,7 @@ static void	save_events()
 	zbx_strcpy_alloc(&sql, &sql_alloc, &sql_offset, ins_event_sql);
 #endif
 
-	for (i = 0; i < events_num; i++)
+	for (i = from; i < events_num; i++)
 	{
 		if (0 == events[i].eventid)
 			events[i].eventid = DBget_maxid("events");
@@ -108,9 +116,9 @@ static void	save_events()
 		zbx_strcpy_alloc(&sql, &sql_alloc, &sql_offset, ins_event_sql);
 #endif
 		zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset,
-			"(" ZBX_FS_UI64 ",%d,%d," ZBX_FS_UI64 ",%d,%d,%d)" ZBX_ROW_DL,
+			"(" ZBX_FS_UI64 ",%d,%d," ZBX_FS_UI64 ",%d,%d,%d,%s)" ZBX_ROW_DL,
 			events[i].eventid, events[i].source, events[i].object, events[i].objectid, events[i].clock,
-			events[i].ns, events[i].value);
+                        events[i].ns, events[i].value, DBsql_id_ins(events[i].parentid));
 	}
 
 #ifdef HAVE_MULTIROW_INSERT
@@ -152,24 +160,32 @@ int	process_events(void)
 	const char	*__function_name = "process_events";
 
 	size_t		i;
+        size_t events_num0;
 
 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() events_num:" ZBX_FS_SIZE_T, __function_name, (zbx_fs_size_t)events_num);
 
 	if (0 != events_num)
 	{
-		save_events();
+		save_events(0);
 
 		process_actions(events, events_num);
 
-		for (i = 0; i < events_num; i++)
+                events_num0 = events_num;
+		for (i = 0; i < events_num0; i++)
 		{
 			if (EVENT_SOURCE_TRIGGERS == events[i].source)
 			{
-				DBupdate_services(events[i].objectid, TRIGGER_VALUE_PROBLEM == events[i].value ?
-						events[i].trigger.priority : 0, events[i].clock);
+                          DBupdate_services(events[i].eventid, events[i].objectid, TRIGGER_VALUE_PROBLEM == events[i].value ?
+                                            events[i].trigger.priority : 0, events[i].clock);
 			}
 		}
 
+                if (events_num0 < events_num) {
+                  /* A number of new events resulted from updating of IT services */
+                  save_events(events_num0);
+                  process_actions(events + events_num0, events_num - events_num0);
+                }
+
 		clean_events();
 	}
 
diff --git a/src/zabbix_server/events.h b/src/zabbix_server/events.h
index 2c2b11b..543d802 100644
--- a/src/zabbix_server/events.h
+++ b/src/zabbix_server/events.h
@@ -23,6 +23,8 @@
 void	add_event(zbx_uint64_t eventid, unsigned char source, unsigned char object, zbx_uint64_t objectid,
 		const zbx_timespec_t *timespec, int value, const char *trigger_description,
 		const char *trigger_expression, unsigned char trigger_priority, unsigned char trigger_type);
+void	add_child_event(zbx_uint64_t parentid, zbx_uint64_t eventid, unsigned char source, unsigned char object,
+                        zbx_uint64_t objectid, const zbx_timespec_t *timespec, int value);
 int	process_events(void);
 
 #endif
diff --git a/database/mysql/schema.sql b/database/mysql/schema.sql
index 785b2db..c536a6d 100644
--- a/database/mysql/schema.sql
+++ b/database/mysql/schema.sql
@@ -1064,6 +1064,7 @@ CREATE TABLE `events` (
 	`value`                  integer         DEFAULT '0'               NOT NULL,
 	`acknowledged`           integer         DEFAULT '0'               NOT NULL,
 	`ns`                     integer         DEFAULT '0'               NOT NULL,
+	`parentid`               bigint unsigned                           NULL,
 	PRIMARY KEY (eventid)
 ) ENGINE=InnoDB;
 CREATE INDEX `events_1` ON `events` (`source`,`object`,`objectid`,`clock`);
@@ -1188,9 +1189,10 @@ CREATE TABLE `escalations` (
 	`esc_step`               integer         DEFAULT '0'               NOT NULL,
 	`status`                 integer         DEFAULT '0'               NOT NULL,
 	`itemid`                 bigint unsigned                           NULL,
+	`serviceid`              bigint unsigned                           NULL,
 	PRIMARY KEY (escalationid)
 ) ENGINE=InnoDB;
-CREATE UNIQUE INDEX `escalations_1` ON `escalations` (`actionid`,`triggerid`,`itemid`,`escalationid`);
+CREATE UNIQUE INDEX `escalations_1` ON `escalations` (`actionid`,`triggerid`,`itemid`,`serviceid`,`escalationid`);
 CREATE TABLE `globalvars` (
 	`globalvarid`            bigint unsigned                           NOT NULL,
 	`snmp_lastsize`          integer         DEFAULT '0'               NOT NULL,
@@ -1379,7 +1381,7 @@ CREATE TABLE `dbversion` (
 	`mandatory`              integer         DEFAULT '0'               NOT NULL,
 	`optional`               integer         DEFAULT '0'               NOT NULL
 ) ENGINE=InnoDB;
-INSERT INTO dbversion VALUES ('2020000','2020000');
+INSERT INTO dbversion VALUES ('2020003','2020003');
 ALTER TABLE `hosts` ADD CONSTRAINT `c_hosts_1` FOREIGN KEY (`proxy_hostid`) REFERENCES `hosts` (`hostid`);
 ALTER TABLE `hosts` ADD CONSTRAINT `c_hosts_2` FOREIGN KEY (`maintenanceid`) REFERENCES `maintenances` (`maintenanceid`);
 ALTER TABLE `hosts` ADD CONSTRAINT `c_hosts_3` FOREIGN KEY (`templateid`) REFERENCES `hosts` (`hostid`) ON DELETE CASCADE;
diff --git a/database/oracle/schema.sql b/database/oracle/schema.sql
index c5e6c52..8413acb 100644
--- a/database/oracle/schema.sql
+++ b/database/oracle/schema.sql
@@ -1064,6 +1064,7 @@ CREATE TABLE events (
 	value                    number(10)      DEFAULT '0'               NOT NULL,
 	acknowledged             number(10)      DEFAULT '0'               NOT NULL,
 	ns                       number(10)      DEFAULT '0'               NOT NULL,
+	parentid                 number(20)                                NULL,
 	PRIMARY KEY (eventid)
 );
 CREATE INDEX events_1 ON events (source,object,objectid,clock);
@@ -1188,9 +1189,10 @@ CREATE TABLE escalations (
 	esc_step                 number(10)      DEFAULT '0'               NOT NULL,
 	status                   number(10)      DEFAULT '0'               NOT NULL,
 	itemid                   number(20)                                NULL,
+	serviceid                number(20)                                NULL,
 	PRIMARY KEY (escalationid)
 );
-CREATE UNIQUE INDEX escalations_1 ON escalations (actionid,triggerid,itemid,escalationid);
+CREATE UNIQUE INDEX escalations_1 ON escalations (actionid,triggerid,itemid,serviceid,escalationid);
 CREATE TABLE globalvars (
 	globalvarid              number(20)                                NOT NULL,
 	snmp_lastsize            number(10)      DEFAULT '0'               NOT NULL,
@@ -1379,7 +1381,7 @@ CREATE TABLE dbversion (
 	mandatory                number(10)      DEFAULT '0'               NOT NULL,
 	optional                 number(10)      DEFAULT '0'               NOT NULL
 );
-INSERT INTO dbversion VALUES ('2020000','2020000');
+INSERT INTO dbversion VALUES ('2020003','2020003');
 CREATE SEQUENCE history_sync_seq
 START WITH 1
 INCREMENT BY 1
diff --git a/database/postgresql/schema.sql b/database/postgresql/schema.sql
index d621cc5..a1cef77 100644
--- a/database/postgresql/schema.sql
+++ b/database/postgresql/schema.sql
@@ -1064,6 +1064,7 @@ CREATE TABLE events (
 	value                    integer         DEFAULT '0'               NOT NULL,
 	acknowledged             integer         DEFAULT '0'               NOT NULL,
 	ns                       integer         DEFAULT '0'               NOT NULL,
+	parentid                 bigint                                    NULL,
 	PRIMARY KEY (eventid)
 );
 CREATE INDEX events_1 ON events (source,object,objectid,clock);
@@ -1188,9 +1189,10 @@ CREATE TABLE escalations (
 	esc_step                 integer         DEFAULT '0'               NOT NULL,
 	status                   integer         DEFAULT '0'               NOT NULL,
 	itemid                   bigint                                    NULL,
+	serviceid                bigint                                    NULL,
 	PRIMARY KEY (escalationid)
 );
-CREATE UNIQUE INDEX escalations_1 ON escalations (actionid,triggerid,itemid,escalationid);
+CREATE UNIQUE INDEX escalations_1 ON escalations (actionid,triggerid,itemid,serviceid,escalationid);
 CREATE TABLE globalvars (
 	globalvarid              bigint                                    NOT NULL,
 	snmp_lastsize            integer         DEFAULT '0'               NOT NULL,
@@ -1379,7 +1381,7 @@ CREATE TABLE dbversion (
 	mandatory                integer         DEFAULT '0'               NOT NULL,
 	optional                 integer         DEFAULT '0'               NOT NULL
 );
-INSERT INTO dbversion VALUES ('2020000','2020000');
+INSERT INTO dbversion VALUES ('2020003','2020003');
 ALTER TABLE ONLY hosts ADD CONSTRAINT c_hosts_1 FOREIGN KEY (proxy_hostid) REFERENCES hosts (hostid);
 ALTER TABLE ONLY hosts ADD CONSTRAINT c_hosts_2 FOREIGN KEY (maintenanceid) REFERENCES maintenances (maintenanceid);
 ALTER TABLE ONLY hosts ADD CONSTRAINT c_hosts_3 FOREIGN KEY (templateid) REFERENCES hosts (hostid) ON DELETE CASCADE;
diff --git a/database/sqlite3/schema.sql b/database/sqlite3/schema.sql
index d9d7710..e87fb06 100644
--- a/database/sqlite3/schema.sql
+++ b/database/sqlite3/schema.sql
@@ -1059,6 +1059,7 @@ CREATE TABLE events (
 	value                    integer         DEFAULT '0'               NOT NULL,
 	acknowledged             integer         DEFAULT '0'               NOT NULL,
 	ns                       integer         DEFAULT '0'               NOT NULL,
+	parentid                 bigint                                    NULL,
 	PRIMARY KEY (eventid)
 );
 CREATE INDEX events_1 ON events (source,object,objectid,clock);
@@ -1182,9 +1183,10 @@ CREATE TABLE escalations (
 	esc_step                 integer         DEFAULT '0'               NOT NULL,
 	status                   integer         DEFAULT '0'               NOT NULL,
 	itemid                   bigint                                    NULL,
+	serviceid                bigint                                    NULL,
 	PRIMARY KEY (escalationid)
 );
-CREATE UNIQUE INDEX escalations_1 ON escalations (actionid,triggerid,itemid,escalationid);
+CREATE UNIQUE INDEX escalations_1 ON escalations (actionid,triggerid,itemid,serviceid,escalationid);
 CREATE TABLE globalvars (
 	globalvarid              bigint                                    NOT NULL,
 	snmp_lastsize            integer         DEFAULT '0'               NOT NULL,
@@ -1373,4 +1375,4 @@ CREATE TABLE dbversion (
 	mandatory                integer         DEFAULT '0'               NOT NULL,
 	optional                 integer         DEFAULT '0'               NOT NULL
 );
-INSERT INTO dbversion VALUES ('2020000','2020000');
+INSERT INTO dbversion VALUES ('2020003','2020003');
