Repository: devtron-labs/devtron
Pull request #3541 (2023-06-21 13:21:52)

docs: yaml for deployment chart
<!--
Type of change: Title of the PR should clearly mention which type of PR is this, you can select any of the below mentioned types:

- docs - yaml for deployment chart


# Description
<!--
docs - yaml for deployment chart
-->


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=devtron-labs_devtron&pullRequest=3541)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=devtron-labs_devtron&pullRequest=3541&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=devtron-labs_devtron&pullRequest=3541&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=devtron-labs_devtron&pullRequest=3541&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=devtron-labs_devtron&pullRequest=3541&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=devtron-labs_devtron&pullRequest=3541&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=devtron-labs_devtron&pullRequest=3541&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=devtron-labs_devtron&pullRequest=3541&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=devtron-labs_devtron&pullRequest=3541&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=devtron-labs_devtron&pullRequest=3541&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=devtron-labs_devtron&pullRequest=3541&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=devtron-labs_devtron&pullRequest=3541&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=devtron-labs_devtron&pullRequest=3541&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=devtron-labs_devtron&pullRequest=3541) No Coverage information  
[![No Duplication information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo-16px.png 'No Duplication information')](https://sonarcloud.io/component_measures?id=devtron-labs_devtron&pullRequest=3541&metric=duplicated_lines_density&view=list) No Duplication information



Patch:
diff --git a/.github/workflows/helm-chart-lint.yaml b/.github/workflows/helm-chart-lint.yaml
index 3b7317c9c..a7103c0cd 100644
--- a/.github/workflows/helm-chart-lint.yaml
+++ b/.github/workflows/helm-chart-lint.yaml
@@ -20,24 +20,47 @@ jobs:
           charts:
             - added|modified: 'scripts/devtron-reference-helm-charts/**/templates/**'
             - added|modified: 'charts/devtron/**/templates/**'
+          files: 
+            - added|modified: 'charts/devtron/*'
+          ref_chart:
+            - added|modified: 'scripts/devtron-reference-helm-charts/*/*'
     - run: echo ${{ steps.filter.outputs.charts }}
+    - run: echo ${{ steps.filter.outputs.files }}
     - uses: actions/setup-python@v2
     - uses: jannekem/run-python-script-action@v1
       id: python
       with:
         script: |
             string='${{ steps.filter.outputs.charts_files }}'
-            location=string.split("templates",1)[0]
-            print(location)
-            set_output("chart_path",location)
+            devtron_chart='${{ steps.filter.outputs.files_files }}'
+            ref_chart='${{ steps.filter.outputs.ref_chart_files }}'
+            print(string)
+            print(devtron_chart)
+            print(ref_chart)
+            split_ref_chart=ref_chart.split("/")
+            print(split_ref_chart)
+            if (string) :
+              location=string.split("templates",1)[0]
+              print(location)
+              set_output("chart_path",location)
+            elif (devtron_chart):
+              set_output("chart_path","charts/devtron/")
+            else :
+              split_ref_chart=split_ref_chart[:-1]
+              print("/".join(split_ref_chart))
+              split_ref_chart="/".join(split_ref_chart)
+              split_ref_chart+="/"
+              set_output("chart_path",split_ref_chart)
     - run: echo ${{ steps.python.outputs.chart_path }}
     - name: Print errors
       if: steps.script.outputs.error == 'true'
       run: |
         printenv "SCRIPT_STDOUT"
         printenv "SCRIPT_STDERR"
+    - name: helm build
+      run: cd  ./${{ steps.python.outputs.chart_path }} && helm dependency build
     - name: helm-check
       uses: igabaydulin/helm-check-action@0.1.4
       env:
         CHART_LOCATION: ./${{ steps.python.outputs.chart_path }}
-        CHART_VALUES: ./${{ steps.python.outputs.chart_path }}values.yaml
\ No newline at end of file
+        CHART_VALUES: ./${{ steps.python.outputs.chart_path }}values.yaml
diff --git a/Wire.go b/Wire.go
index 0467650a5..bf581e611 100644
--- a/Wire.go
+++ b/Wire.go
@@ -374,6 +374,10 @@ func InitializeApp() (*App, error) {
 		wire.Bind(new(notifier.SlackNotificationService), new(*notifier.SlackNotificationServiceImpl)),
 		repository.NewSlackNotificationRepositoryImpl,
 		wire.Bind(new(repository.SlackNotificationRepository), new(*repository.SlackNotificationRepositoryImpl)),
+		notifier.NewWebhookNotificationServiceImpl,
+		wire.Bind(new(notifier.WebhookNotificationService), new(*notifier.WebhookNotificationServiceImpl)),
+		repository.NewWebhookNotificationRepositoryImpl,
+		wire.Bind(new(repository.WebhookNotificationRepository), new(*repository.WebhookNotificationRepositoryImpl)),
 
 		notifier.NewNotificationConfigServiceImpl,
 		wire.Bind(new(notifier.NotificationConfigService), new(*notifier.NotificationConfigServiceImpl)),
diff --git a/api/helm-app/HelmAppService.go b/api/helm-app/HelmAppService.go
index aed1ffc0e..252fb3ebf 100644
--- a/api/helm-app/HelmAppService.go
+++ b/api/helm-app/HelmAppService.go
@@ -795,12 +795,15 @@ func (impl *HelmAppServiceImpl) DecodeAppId(appId string) (*AppIdentifier, error
 	if len(component) != 3 {
 		return nil, fmt.Errorf("malformed app id %s", appId)
 	}
-	clustewrId, err := strconv.Atoi(component[0])
+	clusterId, err := strconv.Atoi(component[0])
 	if err != nil {
 		return nil, err
 	}
+	if clusterId <= 0 {
+		return nil, fmt.Errorf("target cluster is not provided")
+	}
 	return &AppIdentifier{
-		ClusterId:   clustewrId,
+		ClusterId:   clusterId,
 		Namespace:   component[1],
 		ReleaseName: component[2],
 	}, nil
diff --git a/api/restHandler/NotificationRestHandler.go b/api/restHandler/NotificationRestHandler.go
index 586deedd5..f047942dc 100644
--- a/api/restHandler/NotificationRestHandler.go
+++ b/api/restHandler/NotificationRestHandler.go
@@ -45,19 +45,21 @@ import (
 )
 
 const (
-	SLACK_CONFIG_DELETE_SUCCESS_RESP = "Slack config deleted successfully."
-	SES_CONFIG_DELETE_SUCCESS_RESP   = "SES config deleted successfully."
-	SMTP_CONFIG_DELETE_SUCCESS_RESP  = "SMTP config deleted successfully."
+	SLACK_CONFIG_DELETE_SUCCESS_RESP   = "Slack config deleted successfully."
+	WEBHOOK_CONFIG_DELETE_SUCCESS_RESP = "Webhook config deleted successfully."
+	SES_CONFIG_DELETE_SUCCESS_RESP     = "SES config deleted successfully."
+	SMTP_CONFIG_DELETE_SUCCESS_RESP    = "SMTP config deleted successfully."
 )
 
 type NotificationRestHandler interface {
 	SaveNotificationSettings(w http.ResponseWriter, r *http.Request)
 	UpdateNotificationSettings(w http.ResponseWriter, r *http.Request)
-
 	SaveNotificationChannelConfig(w http.ResponseWriter, r *http.Request)
 	FindSESConfig(w http.ResponseWriter, r *http.Request)
 	FindSlackConfig(w http.ResponseWriter, r *http.Request)
 	FindSMTPConfig(w http.ResponseWriter, r *http.Request)
+	FindWebhookConfig(w http.ResponseWriter, r *http.Request)
+	GetWebhookVariables(w http.ResponseWriter, r *http.Request)
 	FindAllNotificationConfig(w http.ResponseWriter, r *http.Request)
 	GetAllNotificationSettings(w http.ResponseWriter, r *http.Request)
 	DeleteNotificationSettings(w http.ResponseWriter, r *http.Request)
@@ -76,6 +78,7 @@ type NotificationRestHandlerImpl struct {
 	validator            *validator.Validate
 	notificationService  notifier.NotificationConfigService
 	slackService         notifier.SlackNotificationService
+	webhookService       notifier.WebhookNotificationService
 	sesService           notifier.SESNotificationService
 	smtpService          notifier.SMTPNotificationService
 	enforcer             casbin.Enforcer
@@ -93,7 +96,7 @@ func NewNotificationRestHandlerImpl(dockerRegistryConfig pipeline.DockerRegistry
 	logger *zap.SugaredLogger, gitRegistryConfig pipeline.GitRegistryConfig,
 	dbConfigService pipeline.DbConfigService, userAuthService user.UserService,
 	validator *validator.Validate, notificationService notifier.NotificationConfigService,
-	slackService notifier.SlackNotificationService, sesService notifier.SESNotificationService, smtpService notifier.SMTPNotificationService,
+	slackService notifier.SlackNotificationService, webhookService notifier.WebhookNotificationService, sesService notifier.SESNotificationService, smtpService notifier.SMTPNotificationService,
 	enforcer casbin.Enforcer, teamService team.TeamService, environmentService cluster.EnvironmentService, pipelineBuilder pipeline.PipelineBuilder,
 	enforcerUtil rbac.EnforcerUtil) *NotificationRestHandlerImpl {
 	return &NotificationRestHandlerImpl{
@@ -105,6 +108,7 @@ func NewNotificationRestHandlerImpl(dockerRegistryConfig pipeline.DockerRegistry
 		validator:            validator,
 		notificationService:  notificationService,
 		slackService:         slackService,
+		webhookService:       webhookService,
 		sesService:           sesService,
 		smtpService:          smtpService,
 		enforcer:             enforcer,
@@ -551,13 +555,46 @@ func (impl NotificationRestHandlerImpl) SaveNotificationChannelConfig(w http.Res
 		}
 		w.Header().Set("Content-Type", "application/json")
 		common.WriteJsonResp(w, nil, res, http.StatusOK)
+	} else if util.Webhook == channelReq.Channel {
+		var webhookReq *notifier.WebhookChannelConfig
+		err = json.NewDecoder(ioutil.NopCloser(bytes.NewBuffer(data))).Decode(&webhookReq)
+		if err != nil {
+			impl.logger.Errorw("request err, SaveNotificationChannelConfig", "err", err, "webhookReq", webhookReq)
+			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+			return
+		}
+
+		err = impl.validator.Struct(webhookReq)
+		if err != nil {
+			impl.logger.Errorw("validation err, SaveNotificationChannelConfig", "err", err, "webhookReq", webhookReq)
+			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+			return
+		}
+
+		// RBAC enforcer applying
+		token := r.Header.Get("token")
+		if ok := impl.enforcer.Enforce(token, casbin.ResourceNotification, casbin.ActionCreate, "*"); !ok {
+			response.WriteResponse(http.StatusForbidden, "FORBIDDEN", w, errors.New("unauthorized"))
+			return
+		}
+		//RBAC enforcer Ends
+
+		res, cErr := impl.webhookService.SaveOrEditNotificationConfig(*webhookReq.WebhookConfigDtos, userId)
+		if cErr != nil {
+			impl.logger.Errorw("service err, SaveNotificationChannelConfig", "err", err, "webhookReq", webhookReq)
+			common.WriteJsonResp(w, cErr, nil, http.StatusInternalServerError)
+			return
+		}
+		w.Header().Set("Content-Type", "application/json")
+		common.WriteJsonResp(w, nil, res, http.StatusOK)
 	}
 }
 
 type ChannelResponseDTO struct {
-	SlackConfigs []*notifier.SlackConfigDto `json:"slackConfigs"`
-	SESConfigs   []*notifier.SESConfigDto   `json:"sesConfigs"`
-	SMTPConfigs  []*notifier.SMTPConfigDto  `json:"smtpConfigs"`
+	SlackConfigs   []*notifier.SlackConfigDto   `json:"slackConfigs"`
+	WebhookConfigs []*notifier.WebhookConfigDto `json:"webhookConfigs"`
+	SESConfigs     []*notifier.SESConfigDto     `json:"sesConfigs"`
+	SMTPConfigs    []*notifier.SMTPConfigDto    `json:"smtpConfigs"`
 }
 
 func (impl NotificationRestHandlerImpl) FindAllNotificationConfig(w http.ResponseWriter, r *http.Request) {
@@ -607,6 +644,18 @@ func (impl NotificationRestHandlerImpl) FindAllNotificationConfig(w http.Respons
 	if pass {
 		channelsResponse.SlackConfigs = slackConfigs
 	}
+	webhookConfigs, fErr := impl.webhookService.FetchAllWebhookNotificationConfig()
+	if fErr != nil && fErr != pg.ErrNoRows {
+		impl.logger.Errorw("service err, FindAllNotificationConfig", "err", err)
+		common.WriteJsonResp(w, fErr, nil, http.StatusInternalServerError)
+		return
+	}
+	if webhookConfigs == nil {
+		webhookConfigs = make([]*notifier.WebhookConfigDto, 0)
+	}
+	if pass {
+		channelsResponse.WebhookConfigs = webhookConfigs
+	}
 	sesConfigs, fErr := impl.sesService.FetchAllSESNotificationConfig()
 	if fErr != nil && fErr != pg.ErrNoRows {
 		impl.logger.Errorw("service err, FindAllNotificationConfig", "err", err)
@@ -717,6 +766,47 @@ func (impl NotificationRestHandlerImpl) FindSMTPConfig(w http.ResponseWriter, r
 	w.Header().Set("Content-Type", "application/json")
 	common.WriteJsonResp(w, fErr, smtpConfig, http.StatusOK)
 }
+func (impl NotificationRestHandlerImpl) FindWebhookConfig(w http.ResponseWriter, r *http.Request) {
+	userId, err := impl.userAuthService.GetLoggedInUser(r)
+	if userId == 0 || err != nil {
+		common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
+		return
+	}
+	vars := mux.Vars(r)
+	id, err := strconv.Atoi(vars["id"])
+	if err != nil {
+		impl.logger.Errorw("request err, FindWebhookConfig", "err", err, "id", id)
+		common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+		return
+	}
+
+	webhookConfig, fErr := impl.webhookService.FetchWebhookNotificationConfigById(id)
+	if fErr != nil && fErr != pg.ErrNoRows {
+		impl.logger.Errorw("service err, FindWebhookConfig, cannot find webhook config", "err", fErr, "id", id)
+		common.WriteJsonResp(w, fErr, nil, http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	common.WriteJsonResp(w, fErr, webhookConfig, http.StatusOK)
+}
+func (impl NotificationRestHandlerImpl) GetWebhookVariables(w http.ResponseWriter, r *http.Request) {
+	userId, err := impl.userAuthService.GetLoggedInUser(r)
+	if userId == 0 || err != nil {
+		common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
+		return
+	}
+
+	webhookVariables, fErr := impl.webhookService.GetWebhookVariables()
+	if fErr != nil && fErr != pg.ErrNoRows {
+		impl.logger.Errorw("service err, GetWebhookVariables, cannot find webhook Variables", "err", fErr)
+		common.WriteJsonResp(w, fErr, nil, http.StatusInternalServerError)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	common.WriteJsonResp(w, fErr, webhookVariables, http.StatusOK)
+}
 
 func (impl NotificationRestHandlerImpl) RecipientListingSuggestion(w http.ResponseWriter, r *http.Request) {
 	userId, err := impl.userAuthService.GetLoggedInUser(r)
@@ -783,6 +873,17 @@ func (impl NotificationRestHandlerImpl) FindAllNotificationConfigAutocomplete(w
 			}
 		}
 
+	} else if cType == string(util.Webhook) {
+		if ok := impl.enforcer.Enforce(token, casbin.ResourceNotification, casbin.ActionGet, "*"); !ok {
+			response.WriteResponse(http.StatusForbidden, "FORBIDDEN", w, errors.New("unauthorized"))
+			return
+		}
+		channelsResponse, err = impl.webhookService.FetchAllWebhookNotificationConfigAutocomplete()
+		if err != nil && err != pg.ErrNoRows {
+			impl.logger.Errorw("service err, FindAllNotificationConfigAutocomplete", "err", err)
+			common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
+			return
+		}
 	} else if cType == string(util.SES) {
 		if ok := impl.enforcer.Enforce(token, casbin.ResourceNotification, casbin.ActionGet, "*"); !ok {
 			response.WriteResponse(http.StatusForbidden, "FORBIDDEN", w, errors.New("unauthorized"))
@@ -929,6 +1030,37 @@ func (impl NotificationRestHandlerImpl) DeleteNotificationChannelConfig(w http.R
 			return
 		}
 		common.WriteJsonResp(w, nil, SLACK_CONFIG_DELETE_SUCCESS_RESP, http.StatusOK)
+	} else if util.Webhook == channelReq.Channel {
+		var deleteReq *notifier.WebhookConfigDto
+		err = json.NewDecoder(ioutil.NopCloser(bytes.NewBuffer(data))).Decode(&deleteReq)
+		if err != nil {
+			impl.logger.Errorw("request err, DeleteNotificationChannelConfig", "err", err, "deleteReq", deleteReq)
+			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+			return
+		}
+
+		err = impl.validator.Struct(deleteReq)
+		if err != nil {
+			impl.logger.Errorw("validation err, DeleteNotificationChannelConfig", "err", err, "deleteReq", deleteReq)
+			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+			return
+		}
+
+		// RBAC enforcer applying
+		token := r.Header.Get("token")
+		if ok := impl.enforcer.Enforce(token, casbin.ResourceNotification, casbin.ActionCreate, "*"); !ok {
+			response.WriteResponse(http.StatusForbidden, "FORBIDDEN", w, errors.New("unauthorized"))
+			return
+		}
+		//RBAC enforcer Ends
+
+		cErr := impl.webhookService.DeleteNotificationConfig(deleteReq, userId)
+		if cErr != nil {
+			impl.logger.Errorw("service err, DeleteNotificationChannelConfig", "err", err, "deleteReq", deleteReq)
+			common.WriteJsonResp(w, cErr, nil, http.StatusInternalServerError)
+			return
+		}
+		common.WriteJsonResp(w, nil, WEBHOOK_CONFIG_DELETE_SUCCESS_RESP, http.StatusOK)
 	} else if util.SES == channelReq.Channel {
 		var deleteReq *notifier.SESConfigDto
 		err = json.NewDecoder(ioutil.NopCloser(bytes.NewBuffer(data))).Decode(&deleteReq)
diff --git a/api/router/NotificationRouter.go b/api/router/NotificationRouter.go
index 1cc7bc82e..f92002127 100644
--- a/api/router/NotificationRouter.go
+++ b/api/router/NotificationRouter.go
@@ -63,6 +63,13 @@ func (impl NotificationRouterImpl) InitNotificationRegRouter(configRouter *mux.R
 	configRouter.Path("/channel/smtp/{id}").
 		HandlerFunc(impl.notificationRestHandler.FindSMTPConfig).
 		Methods("GET")
+	configRouter.Path("/channel/webhook/{id}").
+		HandlerFunc(impl.notificationRestHandler.FindWebhookConfig).
+		Methods("GET")
+	configRouter.Path("/variables").
+		HandlerFunc(impl.notificationRestHandler.GetWebhookVariables).
+		Methods("GET")
+
 	configRouter.Path("/channel").
 		HandlerFunc(impl.notificationRestHandler.DeleteNotificationChannelConfig).
 		Methods("DELETE")
diff --git a/charts/devtron/templates/migrator.yaml b/charts/devtron/templates/migrator.yaml
index 8ce9214f9..a4fcc899b 100644
--- a/charts/devtron/templates/migrator.yaml
+++ b/charts/devtron/templates/migrator.yaml
@@ -1,8 +1,9 @@
 {{- with .Values.components.migrator }}
----
 {{- if $.Capabilities.APIVersions.Has "batch/v1/Job" -}}
+---
 apiVersion: batch/v1
 {{- else -}}
+---
 apiVersion: batch/v1beta1
 {{- end }}
 kind: Job
@@ -58,10 +59,12 @@ spec:
       restartPolicy: OnFailure
   backoffLimit: 20
   activeDeadlineSeconds: 1500
----
+
 {{- if $.Capabilities.APIVersions.Has "batch/v1/Job" -}}
+---
 apiVersion: batch/v1
 {{- else -}}
+---
 apiVersion: batch/v1beta1
 {{- end }}
 kind: Job
diff --git a/client/k8s/application/Application.go b/client/k8s/application/Application.go
index 4fdcd9aa5..ec5326bc5 100644
--- a/client/k8s/application/Application.go
+++ b/client/k8s/application/Application.go
@@ -54,10 +54,11 @@ type K8sRequestBean struct {
 }
 
 type PodLogsRequest struct {
-	SinceTime     *metav1.Time `json:"sinceTime,omitempty"`
-	TailLines     int          `json:"tailLines"`
-	Follow        bool         `json:"follow"`
-	ContainerName string       `json:"containerName"`
+	SinceTime                  *metav1.Time `json:"sinceTime,omitempty"`
+	TailLines                  int          `json:"tailLines"`
+	Follow                     bool         `json:"follow"`
+	ContainerName              string       `json:"containerName"`
+	IsPrevContainerLogsEnabled bool         `json:"previous"`
 }
 
 type ResourceIdentifier struct {
@@ -236,6 +237,7 @@ func (impl K8sClientServiceImpl) GetPodLogs(ctx context.Context, restConfig *res
 		TailLines:  &tailLines,
 		Container:  podLogsRequest.ContainerName,
 		Timestamps: true,
+		Previous:   podLogsRequest.IsPrevContainerLogsEnabled,
 	}
 	if podLogsRequest.SinceTime != nil {
 		podLogOptions.SinceTime = podLogsRequest.SinceTime
diff --git a/docs/user-guide/creating-application/deployment-template/deployment.md b/docs/user-guide/creating-application/deployment-template/deployment.md
index 9a8357fef..50b491513 100644
--- a/docs/user-guide/creating-application/deployment-template/deployment.md
+++ b/docs/user-guide/creating-application/deployment-template/deployment.md
@@ -23,3 +23,803 @@ You can define application behavior by providing information in the following se
 | `Show application metrics` | You can enable `Show application metrics` to see your application's metrics-CPU Service Monitor usage, Memory Usage, Status, Throughput and Latency.<br>Refer [Application Metrics](https://docs.devtron.ai/v/v0.5/usage/applications/app-details/app-metrics) for more detail.</br> |
 
 
+## 1. Yaml File
+
+### Container Ports
+
+This defines ports on which application services will be exposed to other services
+
+```yaml
+ContainerPort:
+  - envoyPort: 8799
+    idleTimeout:
+    name: app
+    port: 8080
+    servicePort: 80
+    nodePort: 32056
+    supportStreaming: true
+    useHTTP2: true
+```
+
+| Key | Description |
+| :--- | :--- |
+| `envoyPort` | envoy port for the container. |
+| `idleTimeout` | the duration of time that a connection is idle before the connection is terminated. |
+| `name` | name of the port. |
+| `port` | port for the container. |
+| `servicePort` | port of the corresponding kubernetes service. |
+| `nodePort` | nodeport of the corresponding kubernetes service. |
+| `supportStreaming` | Used for high performance protocols like grpc where timeout needs to be disabled. |
+| `useHTTP2` | Envoy container can accept HTTP2 requests. |
+
+### EnvVariables
+```yaml
+EnvVariables: []
+```
+To set environment variables for the containers that run in the Pod.
+
+### Liveness Probe
+
+If this check fails, kubernetes restarts the pod. This should return error code in case of non-recoverable error.
+
+```yaml
+LivenessProbe:
+  Path: ""
+  port: 8080
+  initialDelaySeconds: 20
+  periodSeconds: 10
+  successThreshold: 1
+  timeoutSeconds: 5
+  failureThreshold: 3
+  httpHeaders:
+    - name: Custom-Header
+      value: abc
+  scheme: ""
+  tcp: true
+```
+
+| Key | Description |
+| :--- | :--- |
+| `Path` | It define the path where the liveness needs to be checked. |
+| `initialDelaySeconds` | It defines the time to wait before a given container is checked for liveliness. |
+| `periodSeconds` | It defines the time to check a given container for liveness. |
+| `successThreshold` | It defines the number of successes required before a given container is said to fulfil the liveness probe. |
+| `timeoutSeconds` | It defines the time for checking timeout. |
+| `failureThreshold` | It defines the maximum number of failures that are acceptable before a given container is not considered as live. |
+| `httpHeaders` | Custom headers to set in the request. HTTP allows repeated headers,You can override the default headers by defining .httpHeaders for the probe. |
+| `scheme` | Scheme to use for connecting to the host (HTTP or HTTPS). Defaults to HTTP.
+| `tcp` | The kubelet will attempt to open a socket to your container on the specified port. If it can establish a connection, the container is considered healthy. |
+
+
+### MaxUnavailable
+
+```yaml
+  MaxUnavailable: 0
+```
+The maximum number of pods that can be unavailable during the update process. The value of "MaxUnavailable: " can be an absolute number or percentage of the replicas count. The default value of "MaxUnavailable: " is 25%.
+
+### MaxSurge
+
+```yaml
+MaxSurge: 1
+```
+The maximum number of pods that can be created over the desired number of pods. For "MaxSurge: " also, the value can be an absolute number or percentage of the replicas count.
+The default value of "MaxSurge: " is 25%.
+
+### Min Ready Seconds
+
+```yaml
+MinReadySeconds: 60
+```
+This specifies the minimum number of seconds for which a newly created Pod should be ready without any of its containers crashing, for it to be considered available. This defaults to 0 (the Pod will be considered available as soon as it is ready).
+
+### Readiness Probe
+
+If this check fails, kubernetes stops sending traffic to the application. This should return error code in case of errors which can be recovered from if traffic is stopped.
+
+```yaml
+ReadinessProbe:
+  Path: ""
+  port: 8080
+  initialDelaySeconds: 20
+  periodSeconds: 10
+  successThreshold: 1
+  timeoutSeconds: 5
+  failureThreshold: 3
+  httpHeaders:
+    - name: Custom-Header
+      value: abc
+  scheme: ""
+  tcp: true
+```
+
+| Key | Description |
+| :--- | :--- |
+| `Path` | It define the path where the readiness needs to be checked. |
+| `initialDelaySeconds` | It defines the time to wait before a given container is checked for readiness. |
+| `periodSeconds` | It defines the time to check a given container for readiness. |
+| `successThreshold` | It defines the number of successes required before a given container is said to fulfill the readiness probe. |
+| `timeoutSeconds` | It defines the time for checking timeout. |
+| `failureThreshold` | It defines the maximum number of failures that are acceptable before a given container is not considered as ready. |
+| `httpHeaders` | Custom headers to set in the request. HTTP allows repeated headers,You can override the default headers by defining .httpHeaders for the probe. |
+| `scheme` | Scheme to use for connecting to the host (HTTP or HTTPS). Defaults to HTTP.
+| `tcp` | The kubelet will attempt to open a socket to your container on the specified port. If it can establish a connection, the container is considered healthy. |
+
+### Ambassador Mappings
+
+You can create ambassador mappings to access your applications from outside the cluster. At its core a Mapping resource maps a resource to a service.
+
+```yaml
+ambassadorMapping:
+  ambassadorId: "prod-emissary"
+  cors: {}
+  enabled: true
+  hostname: devtron.example.com
+  labels: {}
+  prefix: /
+  retryPolicy: {}
+  rewrite: ""
+  tls:
+    context: "devtron-tls-context"
+    create: false
+    hosts: []
+    secretName: ""
+```
+
+| Key | Description |
+| :--- | :--- |
+| `enabled` | Set true to enable ambassador mapping else set false.|
+| `ambassadorId` | used to specify id for specific ambassador mappings controller. |
+| `cors` | used to specify cors policy to access host for this mapping. |
+| `weight` | used to specify weight for canary ambassador mappings. |
+| `hostname` | used to specify hostname for ambassador mapping. |
+| `prefix` | used to specify path for ambassador mapping. |
+| `labels` | used to provide custom labels for ambassador mapping. |
+| `retryPolicy` | used to specify retry policy for ambassador mapping. |
+| `corsPolicy` | Provide cors headers on flagger resource. |
+| `rewrite` | used to specify whether to redirect the path of this mapping and where. |
+| `tls` | used to create or define ambassador TLSContext resource. |
+| `extraSpec` | used to provide extra spec values which not present in deployment template for ambassador resource. |
+
+### Autoscaling
+
+This is connected to HPA and controls scaling up and down in response to request load.
+
+```yaml
+autoscaling:
+  enabled: false
+  MinReplicas: 1
+  MaxReplicas: 2
+  TargetCPUUtilizationPercentage: 90
+  TargetMemoryUtilizationPercentage: 80
+  extraMetrics: []
+```
+
+| Key | Description |
+| :--- | :--- |
+| `enabled` | Set true to enable autoscaling else set false.|
+| `MinReplicas` | Minimum number of replicas allowed for scaling. |
+| `MaxReplicas` | Maximum number of replicas allowed for scaling. |
+| `TargetCPUUtilizationPercentage` | The target CPU utilization that is expected for a container. |
+| `TargetMemoryUtilizationPercentage` | The target memory utilization that is expected for a container. |
+| `extraMetrics` | Used to give external metrics for autoscaling. |
+
+### Flagger
+
+You can use flagger for canary releases with deployment objects. It supports flexible traffic routing with istio service mesh as well.
+
+```yaml
+flaggerCanary:
+  addOtherGateways: []
+  addOtherHosts: []
+  analysis:
+    interval: 15s
+    maxWeight: 50
+    stepWeight: 5
+    threshold: 5
+  annotations: {}
+  appProtocol: http
+  corsPolicy:
+    allowCredentials: false
+    allowHeaders:
+      - x-some-header
+    allowMethods:
+      - GET
+    allowOrigin:
+      - example.com
+    maxAge: 24h
+  createIstioGateway:
+    annotations: {}
+    enabled: false
+    host: example.com
+    labels: {}
+    tls:
+      enabled: false
+      secretName: example-tls-secret
+  enabled: false
+  gatewayRefs: null
+  headers:
+    request:
+      add:
+        x-some-header: value
+  labels: {}
+  loadtest:
+    enabled: true
+    url: http://flagger-loadtester.istio-system/
+  match:
+    - uri:
+        prefix: /
+  port: 8080
+  portDiscovery: true
+  retries: null
+  rewriteUri: /
+  targetPort: 8080
+  thresholds:
+    latency: 500
+    successRate: 90
+  timeout: null
+```
+
+| Key | Description |
+| :--- | :--- |
+| `enabled` | Set true to enable canary releases using flagger else set false.|
+| `addOtherGateways` | To provide multiple istio gateways for flagger. |
+| `addOtherHosts` | Add multiple hosts for istio service mesh with flagger. |
+| `analysis` | Define how the canary release should progresss and at what interval. |
+| `annotations` | Annotation to add on flagger resource. |
+| `labels` | Labels to add on flagger resource. |
+| `appProtocol` | Protocol to use for canary. |
+| `corsPolicy` | Provide cors headers on flagger resource. |
+| `createIstioGateway` | Set to true if you want to create istio gateway as well with flagger. |
+| `headers` | Add headers if any. |
+| `loadtest` | Enable load testing for your canary release. |
+
+
+
+### Fullname Override
+
+```yaml
+fullnameOverride: app-name
+```
+`fullnameOverride` replaces the release fullname created by default by devtron, which is used to construct Kubernetes object names. By default, devtron uses {app-name}-{environment-name} as release fullname.
+
+### Image
+
+```yaml
+image:
+  pullPolicy: IfNotPresent
+```
+
+Image is used to access images in kubernetes, pullpolicy is used to define the instances calling the image, here the image is pulled when the image is not present,it can also be set as "Always".
+
+### imagePullSecrets
+
+`imagePullSecrets` contains the docker credentials that are used for accessing a registry.
+
+```yaml
+imagePullSecrets:
+  - regcred
+```
+regcred is the secret that contains the docker credentials that are used for accessing a registry. Devtron will not create this secret automatically, you'll have to create this secret using dt-secrets helm chart in the App store or create one using kubectl. You can follow this documentation Pull an Image from a Private Registry [https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) .
+
+### Ingress
+
+This allows public access to the url, please ensure you are using right nginx annotation for nginx class, its default value is nginx
+
+```yaml
+ingress:
+  enabled: false
+  # For K8s 1.19 and above use ingressClassName instead of annotation kubernetes.io/ingress.class:
+  className: nginx
+  annotations: {}
+  hosts:
+      - host: example1.com
+        paths:
+            - /example
+      - host: example2.com
+        paths:
+            - /example2
+            - /example2/healthz
+  tls: []
+```
+Legacy deployment-template ingress format
+
+```yaml
+ingress:
+  enabled: false
+  # For K8s 1.19 and above use ingressClassName instead of annotation kubernetes.io/ingress.class:
+  ingressClassName: nginx-internal
+  annotations: {}
+  path: ""
+  host: ""
+  tls: []
+```
+
+| Key | Description |
+| :--- | :--- |
+| `enabled` | Enable or disable ingress |
+| `annotations` | To configure some options depending on the Ingress controller |
+| `path` | Path name |
+| `host` | Host name |
+| `tls` | It contains security details |
+
+### Ingress Internal
+
+This allows private access to the url, please ensure you are using right nginx annotation for nginx class, its default value is nginx
+
+```yaml
+ingressInternal:
+  enabled: false
+  # For K8s 1.19 and above use ingressClassName instead of annotation kubernetes.io/ingress.class:
+  ingressClassName: nginx-internal
+  annotations: {}
+  hosts:
+      - host: example1.com
+        paths:
+            - /example
+      - host: example2.com
+        paths:
+            - /example2
+            - /example2/healthz
+  tls: []
+```
+
+| Key | Description |
+| :--- | :--- |
+| `enabled` | Enable or disable ingress |
+| `annotations` | To configure some options depending on the Ingress controller |
+| `path` | Path name |
+| `host` | Host name |
+| `tls` | It contains security details |
+
+### Init Containers
+```yaml
+initContainers: 
+  - reuseContainerImage: true
+    securityContext:
+      runAsUser: 1000
+      runAsGroup: 3000
+      fsGroup: 2000
+    volumeMounts:
+     - mountPath: /etc/ls-oms
+       name: ls-oms-cm-vol
+   command:
+     - flyway
+     - -configFiles=/etc/ls-oms/flyway.conf
+     - migrate
+
+  - name: nginx
+    image: nginx:1.14.2
+    securityContext:
+      privileged: true
+    ports:
+    - containerPort: 80
+    command: ["/usr/local/bin/nginx"]
+    args: ["-g", "daemon off;"]
+```
+Specialized containers that run before app containers in a Pod. Init containers can contain utilities or setup scripts not present in an app image. One can use base image inside initContainer by setting the reuseContainerImage flag to `true`.
+
+### Istio
+
+Istio is a service mesh which simplifies observability, traffic management, security and much more with it's virtual services and gateways.
+
+```yaml
+istio:
+  enable: true
+  gateway:
+    annotations: {}
+    enabled: false
+    host: example.com
+    labels: {}
+    tls:
+      enabled: false
+      secretName: example-tls-secret
+  virtualService:
+    annotations: {}
+    enabled: false
+    gateways: []
+    hosts: []
+    http:
+      - corsPolicy:
+          allowCredentials: false
+          allowHeaders:
+            - x-some-header
+          allowMethods:
+            - GET
+          allowOrigin:
+            - example.com
+          maxAge: 24h
+        headers:
+          request:
+            add:
+              x-some-header: value
+        match:
+          - uri:
+              prefix: /v1
+          - uri:
+              prefix: /v2
+        retries:
+          attempts: 2
+          perTryTimeout: 3s
+        rewriteUri: /
+        route:
+          - destination:
+              host: service1
+              port: 80
+        timeout: 12s
+      - route:
+          - destination:
+              host: service2
+    labels: {}
+```
+
+### Pause For Seconds Before Switch Active
+```yaml
+pauseForSecondsBeforeSwitchActive: 30
+```
+To wait for given period of time before switch active the container.
+
+### Resources
+
+These define minimum and maximum RAM and CPU available to the application.
+
+```yaml
+resources:
+  limits:
+    cpu: "1"
+    memory: "200Mi"
+  requests:
+    cpu: "0.10"
+    memory: "100Mi"
+```
+
+Resources are required to set CPU and memory usage.
+
+#### Limits
+
+Limits make sure a container never goes above a certain value. The container is only allowed to go up to the limit, and then it is restricted.
+
+#### Requests
+
+Requests are what the container is guaranteed to get.
+
+### Service
+
+This defines annotations and the type of service, optionally can define name also.
+
+```yaml
+  service:
+    type: ClusterIP
+    annotations: {}
+```
+
+### Volumes
+
+```yaml
+volumes:
+  - name: log-volume
+    emptyDir: {}
+  - name: logpv
+    persistentVolumeClaim:
+      claimName: logpvc
+```
+
+It is required when some values need to be read from or written to an external disk.
+
+### Volume Mounts
+
+```yaml
+volumeMounts:
+  - mountPath: /var/log/nginx/
+    name: log-volume 
+  - mountPath: /mnt/logs
+    name: logpvc
+    subPath: employee  
+```
+
+It is used to provide mounts to the volume.
+
+### Affinity and anti-affinity
+
+```yaml
+Spec:
+  Affinity:
+    Key:
+    Values:
+```
+
+Spec is used to define the desire state of the given container.
+
+Node Affinity allows you to constrain which nodes your pod is eligible to schedule on, based on labels of the node.
+
+Inter-pod affinity allow you to constrain which nodes your pod is eligible to be scheduled based on labels on pods.
+
+#### Key
+
+Key part of the label for node selection, this should be same as that on node. Please confirm with devops team.
+
+#### Values
+
+Value part of the label for node selection, this should be same as that on node. Please confirm with devops team.
+
+### Tolerations
+
+```yaml
+tolerations:
+ - key: "key"
+   operator: "Equal"
+   value: "value"
+   effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)"
+```
+
+Taints are the opposite, they allow a node to repel a set of pods.
+
+A given pod can access the given node and avoid the given taint only if the given pod satisfies a given taint.
+
+Taints and tolerations are a mechanism which work together that allows you to ensure that pods are not placed on inappropriate nodes. Taints are added to nodes, while tolerations are defined in the pod specification. When you taint a node, it will repel all the pods except those that have a toleration for that taint. A node can have one or many taints associated with it.
+
+### Arguments
+
+```yaml
+args:
+  enabled: false
+  value: []
+```
+
+This is used to give arguments to command.
+
+### Command
+
+```yaml
+command:
+  enabled: false
+  value: []
+```
+
+It contains the commands for the server.
+
+| Key | Description |
+| :--- | :--- |
+| `enabled` | To enable or disable the command. |
+| `value` | It contains the commands. |
+
+
+### Containers
+Containers section can be used to run side-car containers along with your main container within same pod. Containers running within same pod can share volumes and IP Address and can address each other @localhost. We can use base image inside container by setting the reuseContainerImage flag to `true`.
+
+```yaml
+    containers:
+      - name: nginx
+        image: nginx:1.14.2
+        ports:
+        - containerPort: 80
+        command: ["/usr/local/bin/nginx"]
+        args: ["-g", "daemon off;"]
+      - reuseContainerImage: true
+        securityContext:
+          runAsUser: 1000
+          runAsGroup: 3000
+          fsGroup: 2000
+        volumeMounts:
+        - mountPath: /etc/ls-oms
+          name: ls-oms-cm-vol
+        command:
+          - flyway
+          - -configFiles=/etc/ls-oms/flyway.conf
+          - migrate
+```
+
+### Prometheus
+
+```yaml
+  prometheus:
+    release: monitoring
+```
+
+It is a kubernetes monitoring tool and the name of the file to be monitored as monitoring in the given case.It describes the state of the prometheus.
+
+### rawYaml
+
+```yaml
+rawYaml: 
+  - apiVersion: v1
+    kind: Service
+    metadata:
+      name: my-service
+    spec:
+      selector:
+        app: MyApp
+      ports:
+        - protocol: TCP
+          port: 80
+          targetPort: 9376
+      type: ClusterIP
+```
+Accepts an array of Kubernetes objects. You can specify any kubernetes yaml here and it will be applied when your app gets deployed.
+
+### Grace Period
+
+```yaml
+GracePeriod: 30
+```
+Kubernetes waits for the specified time called the termination grace period before terminating the pods. By default, this is 30 seconds. If your pod usually takes longer than 30 seconds to shut down gracefully, make sure you increase the `GracePeriod`.
+
+A Graceful termination in practice means that your application needs to handle the SIGTERM message and begin shutting down when it receives it. This means saving all data that needs to be saved, closing down network connections, finishing any work that is left, and other similar tasks.
+
+There are many reasons why Kubernetes might terminate a perfectly healthy container. If you update your deployment with a rolling update, Kubernetes slowly terminates old pods while spinning up new ones. If you drain a node, Kubernetes terminates all pods on that node. If a node runs out of resources, Kubernetes terminates pods to free those resources. It’s important that your application handle termination gracefully so that there is minimal impact on the end user and the time-to-recovery is as fast as possible.
+
+
+### Server
+
+```yaml
+server:
+  deployment:
+    image_tag: 1-95a53
+    image: ""
+```
+
+It is used for providing server configurations.
+
+#### Deployment
+
+It gives the details for deployment.
+
+| Key | Description |
+| :--- | :--- |
+| `image_tag` | It is the image tag |
+| `image` | It is the URL of the image |
+
+### Service Monitor
+
+```yaml
+servicemonitor:
+      enabled: true
+      path: /abc
+      scheme: 'http'
+      interval: 30s
+      scrapeTimeout: 20s
+      metricRelabelings:
+        - sourceLabels: [namespace]
+          regex: '(.*)'
+          replacement: myapp
+          targetLabel: target_namespace
+```
+
+It gives the set of targets to be monitored.
+
+### Db Migration Config
+
+```yaml
+dbMigrationConfig:
+  enabled: false
+```
+
+It is used to configure database migration.
+
+
+### KEDA Autoscaling
+[KEDA](https://keda.sh) is a Kubernetes-based Event Driven Autoscaler. With KEDA, you can drive the scaling of any container in Kubernetes based on the number of events needing to be processed. KEDA can be installed into any Kubernetes cluster and can work alongside standard Kubernetes components like the Horizontal Pod Autoscaler(HPA).
+
+Example for autosccaling with KEDA using Prometheus metrics is given below:
+```yaml
+kedaAutoscaling:
+  enabled: true
+  minReplicaCount: 1
+  maxReplicaCount: 2
+  idleReplicaCount: 0
+  pollingInterval: 30
+  advanced:
+    restoreToOriginalReplicaCount: true
+    horizontalPodAutoscalerConfig:
+      behavior:
+        scaleDown:
+          stabilizationWindowSeconds: 300
+          policies:
+          - type: Percent
+            value: 100
+            periodSeconds: 15
+  triggers: 
+    - type: prometheus
+      metadata:
+        serverAddress:  http://<prometheus-host>:9090
+        metricName: http_request_total
+        query: envoy_cluster_upstream_rq{appId="300", cluster_name="300-0", container="envoy",}
+        threshold: "50"
+  triggerAuthentication:
+    enabled: false
+    name:
+    spec: {}
+  authenticationRef: {}
+```
+Example for autosccaling with KEDA based on kafka is given below :
+```yaml
+kedaAutoscaling:
+  enabled: true
+  minReplicaCount: 1
+  maxReplicaCount: 2
+  idleReplicaCount: 0
+  pollingInterval: 30
+  advanced: {}
+  triggers: 
+    - type: kafka
+      metadata:
+        bootstrapServers: b-2.kafka-msk-dev.example.c2.kafka.ap-southeast-1.amazonaws.com:9092,b-3.kafka-msk-dev.example.c2.kafka.ap-southeast-1.amazonaws.com:9092,b-1.kafka-msk-dev.example.c2.kafka.ap-southeast-1.amazonaws.com:9092
+        topic: Orders-Service-ESP.info
+        lagThreshold: "100"
+        consumerGroup: oders-remove-delivered-packages
+        allowIdleConsumers: "true"
+  triggerAuthentication:
+    enabled: true
+    name: keda-trigger-auth-kafka-credential
+    spec:
+      secretTargetRef:
+        - parameter: sasl
+          name: keda-kafka-secrets
+          key: sasl
+        - parameter: username
+          name: keda-kafka-secrets
+          key: username
+  authenticationRef: 
+    name: keda-trigger-auth-kafka-credential
+```
+
+### Security Context
+A security context defines privilege and access control settings for a Pod or Container.
+
+To add a security context for main container:
+```yaml
+containerSecurityContext:
+  allowPrivilegeEscalation: false
+```
+
+To add a security context on pod level:
+```yaml
+podSecurityContext:
+  runAsUser: 1000
+  runAsGroup: 3000
+  fsGroup: 2000
+```
+
+### Topology Spread Constraints
+You can use topology spread constraints to control how Pods are spread across your cluster among failure-domains such as regions, zones, nodes, and other user-defined topology domains. This can help to achieve high availability as well as efficient resource utilization.
+
+```yaml
+topologySpreadConstraints:
+  - maxSkew: 1
+    topologyKey: zone
+    whenUnsatisfiable: DoNotSchedule
+    autoLabelSelector: true
+    customLabelSelector: {}
+```
+
+### Deployment Metrics
+
+It gives the realtime metrics of the deployed applications
+
+| Key | Description |
+| :--- | :--- |
+| `Deployment Frequency` | It shows how often this app is deployed to production |
+| `Change Failure Rate` | It shows how often the respective pipeline fails. |
+| `Mean Lead Time` | It shows the average time taken to deliver a change to production. |
+| `Mean Time to Recovery` | It shows the average time taken to fix a failed pipeline. |
+
+## 2. Show application metrics
+
+If you want to see application metrics like different HTTP status codes metrics, application throughput, latency, response time. Enable the Application metrics from below the deployment template Save button. After enabling it, you should be able to see all metrics on App detail page. By default it remains disabled.
+![](../../../.gitbook/assets/deployment_application_metrics%20%282%29.png)
+
+Once all the Deployment template configurations are done, click on `Save` to save your deployment configuration. Now you are ready to create [Workflow](workflow/) to do CI/CD.
+
+### Helm Chart Json Schema 
+
+Helm Chart [json schema](../../../scripts/devtron-reference-helm-charts/reference-chart_4-11-0/schema.json) is used to validate the deployment template values.
+
+### Other Validations in Json Schema
+
+The values of CPU and Memory in limits must be greater than or equal to in requests respectively. Similarly, In case of envoyproxy, the values of limits are greater than or equal to requests as mentioned below.
+```
+resources.limits.cpu >= resources.requests.cpu
+resources.limits.memory >= resources.requests.memory
+envoyproxy.resources.limits.cpu >= envoyproxy.resources.requests.cpu
+envoyproxy.resources.limits.memory >= envoyproxy.resources.requests.memory
+```
diff --git a/docs/user-guide/creating-application/deployment-template/rollout-deployment.md b/docs/user-guide/creating-application/deployment-template/rollout-deployment.md
index f5b4b7ef4..34b246127 100644
--- a/docs/user-guide/creating-application/deployment-template/rollout-deployment.md
+++ b/docs/user-guide/creating-application/deployment-template/rollout-deployment.md
@@ -792,9 +792,16 @@ envoyproxy.resources.limits.memory >= envoyproxy.resources.requests.memory
 ## Addon features in Deployment Template Chart version 4.11.0
 
 ### KEDA Autoscaling
+
+**Prerequisite:** KEDA contoller should be installed in the cluster. To install KEDA controller using Helm, navigate to chart store and search for `keda` chart and deploy it. You can follow this [documentation](../../deploy-chart/deployment-of-charts.md) for deploying a Helm chart on Devtron.
+
+KEDA Helm repo : https://kedacore.github.io/charts
+
+
 [KEDA](https://keda.sh) is a Kubernetes-based Event Driven Autoscaler. With KEDA, you can drive the scaling of any container in Kubernetes based on the number of events needing to be processed. KEDA can be installed into any Kubernetes cluster and can work alongside standard Kubernetes components like the Horizontal Pod Autoscaler(HPA).
 
-Example for autosccaling with KEDA using Prometheus metrics is given below:
+
+Example for autoscaling with KEDA using Prometheus metrics is given below:
 ```yaml
 kedaAutoscaling:
   enabled: true
@@ -825,7 +832,9 @@ kedaAutoscaling:
     spec: {}
   authenticationRef: {}
 ```
+
 Example for autosccaling with KEDA based on kafka is given below :
+
 ```yaml
 kedaAutoscaling:
   enabled: true
diff --git a/docs/user-guide/creating-application/workflow/cd-pipeline.md b/docs/user-guide/creating-application/workflow/cd-pipeline.md
index 73baf61b3..c393dfd2c 100644
--- a/docs/user-guide/creating-application/workflow/cd-pipeline.md
+++ b/docs/user-guide/creating-application/workflow/cd-pipeline.md
@@ -127,7 +127,9 @@ When deploying an application, we often need to perform additional tasks before
 To enable the "Execute in application environment" option, follow these steps:
 
 1. Go to the chart store and search for the devtron-in-clustercd chart.
+
 ![](https://devtron-public-asset.s3.us-east-2.amazonaws.com/images/creating-application/workflow-cd-pipeline/devtron-incluster-chart.jpg)
+
 2. Configure the chart according to your requirements and deploy it in the target cluster.
 3. After the deployment, edit the devtron-cm configmap and add the following key-value pair:
 
diff --git a/internal/sql/repository/WebhookNotificationRepository.go b/internal/sql/repository/WebhookNotificationRepository.go
new file mode 100644
index 000000000..86be8b4ff
--- /dev/null
+++ b/internal/sql/repository/WebhookNotificationRepository.go
@@ -0,0 +1,81 @@
+package repository
+
+import (
+	"github.com/devtron-labs/devtron/pkg/sql"
+	"github.com/go-pg/pg"
+)
+
+type WebhookNotificationRepository interface {
+	FindOne(id int) (*WebhookConfig, error)
+	UpdateWebhookConfig(webhookConfig *WebhookConfig) (*WebhookConfig, error)
+	SaveWebhookConfig(webhookConfig *WebhookConfig) (*WebhookConfig, error)
+	FindAll() ([]WebhookConfig, error)
+	FindByName(value string) ([]WebhookConfig, error)
+	FindByIds(ids []*int) ([]*WebhookConfig, error)
+	MarkWebhookConfigDeleted(webhookConfig *WebhookConfig) error
+}
+
+type WebhookNotificationRepositoryImpl struct {
+	dbConnection *pg.DB
+}
+
+func NewWebhookNotificationRepositoryImpl(dbConnection *pg.DB) *WebhookNotificationRepositoryImpl {
+	return &WebhookNotificationRepositoryImpl{dbConnection: dbConnection}
+}
+
+type WebhookConfig struct {
+	tableName   struct{}               `sql:"webhook_config" pg:",discard_unknown_columns"`
+	Id          int                    `sql:"id,pk"`
+	WebHookUrl  string                 `sql:"web_hook_url"`
+	ConfigName  string                 `sql:"config_name"`
+	Header      map[string]interface{} `sql:"header"`
+	Payload     map[string]interface{} `sql:"payload"`
+	Description string                 `sql:"description"`
+	OwnerId     int32                  `sql:"owner_id"`
+	Active      bool                   `sql:"active"`
+	Deleted     bool                   `sql:"deleted,notnull"`
+	sql.AuditLog
+}
+
+func (impl *WebhookNotificationRepositoryImpl) FindOne(id int) (*WebhookConfig, error) {
+	details := &WebhookConfig{}
+	err := impl.dbConnection.Model(details).Where("id = ?", id).
+		Where("deleted = ?", false).Select()
+
+	return details, err
+}
+
+func (impl *WebhookNotificationRepositoryImpl) FindAll() ([]WebhookConfig, error) {
+	var webhookConfigs []WebhookConfig
+	err := impl.dbConnection.Model(&webhookConfigs).
+		Where("deleted = ?", false).Select()
+	return webhookConfigs, err
+}
+
+func (impl *WebhookNotificationRepositoryImpl) UpdateWebhookConfig(webhookConfig *WebhookConfig) (*WebhookConfig, error) {
+	return webhookConfig, impl.dbConnection.Update(webhookConfig)
+}
+
+func (impl *WebhookNotificationRepositoryImpl) SaveWebhookConfig(webhookConfig *WebhookConfig) (*WebhookConfig, error) {
+	return webhookConfig, impl.dbConnection.Insert(webhookConfig)
+}
+
+func (impl *WebhookNotificationRepositoryImpl) FindByName(value string) ([]WebhookConfig, error) {
+	var webhookConfigs []WebhookConfig
+	err := impl.dbConnection.Model(&webhookConfigs).Where(`config_name like ?`, "%"+value+"%").
+		Where("deleted = ?", false).Select()
+	return webhookConfigs, err
+
+}
+
+func (repo *WebhookNotificationRepositoryImpl) FindByIds(ids []*int) ([]*WebhookConfig, error) {
+	var objects []*WebhookConfig
+	err := repo.dbConnection.Model(&objects).Where("id in (?)", pg.In(ids)).
+		Where("deleted = ?", false).Select()
+	return objects, err
+}
+
+func (impl *WebhookNotificationRepositoryImpl) MarkWebhookConfigDeleted(webhookConfig *WebhookConfig) error {
+	webhookConfig.Deleted = true
+	return impl.dbConnection.Update(webhookConfig)
+}
diff --git a/internal/sql/repository/mocks/NotificationSettingsRepository.go b/internal/sql/repository/mocks/NotificationSettingsRepository.go
new file mode 100644
index 000000000..f64a06e43
--- /dev/null
+++ b/internal/sql/repository/mocks/NotificationSettingsRepository.go
@@ -0,0 +1,438 @@
+// Code generated by mockery v2.20.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	pg "github.com/go-pg/pg"
+	mock "github.com/stretchr/testify/mock"
+
+	repository "github.com/devtron-labs/devtron/internal/sql/repository"
+)
+
+// NotificationSettingsRepository is an autogenerated mock type for the NotificationSettingsRepository type
+type NotificationSettingsRepository struct {
+	mock.Mock
+}
+
+// DeleteNotificationSettingsByConfigId provides a mock function with given fields: viewId, tx
+func (_m *NotificationSettingsRepository) DeleteNotificationSettingsByConfigId(viewId int, tx *pg.Tx) (int, error) {
+	ret := _m.Called(viewId, tx)
+
+	var r0 int
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int, *pg.Tx) (int, error)); ok {
+		return rf(viewId, tx)
+	}
+	if rf, ok := ret.Get(0).(func(int, *pg.Tx) int); ok {
+		r0 = rf(viewId, tx)
+	} else {
+		r0 = ret.Get(0).(int)
+	}
+
+	if rf, ok := ret.Get(1).(func(int, *pg.Tx) error); ok {
+		r1 = rf(viewId, tx)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// DeleteNotificationSettingsViewById provides a mock function with given fields: id, tx
+func (_m *NotificationSettingsRepository) DeleteNotificationSettingsViewById(id int, tx *pg.Tx) (int, error) {
+	ret := _m.Called(id, tx)
+
+	var r0 int
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int, *pg.Tx) (int, error)); ok {
+		return rf(id, tx)
+	}
+	if rf, ok := ret.Get(0).(func(int, *pg.Tx) int); ok {
+		r0 = rf(id, tx)
+	} else {
+		r0 = ret.Get(0).(int)
+	}
+
+	if rf, ok := ret.Get(1).(func(int, *pg.Tx) error); ok {
+		r1 = rf(id, tx)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FetchNotificationSettingGroupBy provides a mock function with given fields: viewId
+func (_m *NotificationSettingsRepository) FetchNotificationSettingGroupBy(viewId int) ([]repository.NotificationSettings, error) {
+	ret := _m.Called(viewId)
+
+	var r0 []repository.NotificationSettings
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int) ([]repository.NotificationSettings, error)); ok {
+		return rf(viewId)
+	}
+	if rf, ok := ret.Get(0).(func(int) []repository.NotificationSettings); ok {
+		r0 = rf(viewId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]repository.NotificationSettings)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(int) error); ok {
+		r1 = rf(viewId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindAll provides a mock function with given fields: offset, size
+func (_m *NotificationSettingsRepository) FindAll(offset int, size int) ([]*repository.NotificationSettingsView, error) {
+	ret := _m.Called(offset, size)
+
+	var r0 []*repository.NotificationSettingsView
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int, int) ([]*repository.NotificationSettingsView, error)); ok {
+		return rf(offset, size)
+	}
+	if rf, ok := ret.Get(0).(func(int, int) []*repository.NotificationSettingsView); ok {
+		r0 = rf(offset, size)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*repository.NotificationSettingsView)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(int, int) error); ok {
+		r1 = rf(offset, size)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindNSViewCount provides a mock function with given fields:
+func (_m *NotificationSettingsRepository) FindNSViewCount() (int, error) {
+	ret := _m.Called()
+
+	var r0 int
+	var r1 error
+	if rf, ok := ret.Get(0).(func() (int, error)); ok {
+		return rf()
+	}
+	if rf, ok := ret.Get(0).(func() int); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Get(0).(int)
+	}
+
+	if rf, ok := ret.Get(1).(func() error); ok {
+		r1 = rf()
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindNotificationSettingBuildOptions provides a mock function with given fields: settingRequest
+func (_m *NotificationSettingsRepository) FindNotificationSettingBuildOptions(settingRequest *repository.SearchRequest) ([]*repository.SettingOptionDTO, error) {
+	ret := _m.Called(settingRequest)
+
+	var r0 []*repository.SettingOptionDTO
+	var r1 error
+	if rf, ok := ret.Get(0).(func(*repository.SearchRequest) ([]*repository.SettingOptionDTO, error)); ok {
+		return rf(settingRequest)
+	}
+	if rf, ok := ret.Get(0).(func(*repository.SearchRequest) []*repository.SettingOptionDTO); ok {
+		r0 = rf(settingRequest)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*repository.SettingOptionDTO)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(*repository.SearchRequest) error); ok {
+		r1 = rf(settingRequest)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindNotificationSettingDeploymentOptions provides a mock function with given fields: settingRequest
+func (_m *NotificationSettingsRepository) FindNotificationSettingDeploymentOptions(settingRequest *repository.SearchRequest) ([]*repository.SettingOptionDTO, error) {
+	ret := _m.Called(settingRequest)
+
+	var r0 []*repository.SettingOptionDTO
+	var r1 error
+	if rf, ok := ret.Get(0).(func(*repository.SearchRequest) ([]*repository.SettingOptionDTO, error)); ok {
+		return rf(settingRequest)
+	}
+	if rf, ok := ret.Get(0).(func(*repository.SearchRequest) []*repository.SettingOptionDTO); ok {
+		r0 = rf(settingRequest)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*repository.SettingOptionDTO)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(*repository.SearchRequest) error); ok {
+		r1 = rf(settingRequest)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindNotificationSettingsByConfigIdAndConfigType provides a mock function with given fields: configId, configType
+func (_m *NotificationSettingsRepository) FindNotificationSettingsByConfigIdAndConfigType(configId int, configType string) ([]*repository.NotificationSettings, error) {
+	ret := _m.Called(configId, configType)
+
+	var r0 []*repository.NotificationSettings
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int, string) ([]*repository.NotificationSettings, error)); ok {
+		return rf(configId, configType)
+	}
+	if rf, ok := ret.Get(0).(func(int, string) []*repository.NotificationSettings); ok {
+		r0 = rf(configId, configType)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*repository.NotificationSettings)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(int, string) error); ok {
+		r1 = rf(configId, configType)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindNotificationSettingsByViewId provides a mock function with given fields: viewId
+func (_m *NotificationSettingsRepository) FindNotificationSettingsByViewId(viewId int) ([]repository.NotificationSettings, error) {
+	ret := _m.Called(viewId)
+
+	var r0 []repository.NotificationSettings
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int) ([]repository.NotificationSettings, error)); ok {
+		return rf(viewId)
+	}
+	if rf, ok := ret.Get(0).(func(int) []repository.NotificationSettings); ok {
+		r0 = rf(viewId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]repository.NotificationSettings)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(int) error); ok {
+		r1 = rf(viewId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindNotificationSettingsViewById provides a mock function with given fields: id
+func (_m *NotificationSettingsRepository) FindNotificationSettingsViewById(id int) (*repository.NotificationSettingsView, error) {
+	ret := _m.Called(id)
+
+	var r0 *repository.NotificationSettingsView
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int) (*repository.NotificationSettingsView, error)); ok {
+		return rf(id)
+	}
+	if rf, ok := ret.Get(0).(func(int) *repository.NotificationSettingsView); ok {
+		r0 = rf(id)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.NotificationSettingsView)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(int) error); ok {
+		r1 = rf(id)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindNotificationSettingsViewByIds provides a mock function with given fields: id
+func (_m *NotificationSettingsRepository) FindNotificationSettingsViewByIds(id []*int) ([]*repository.NotificationSettingsView, error) {
+	ret := _m.Called(id)
+
+	var r0 []*repository.NotificationSettingsView
+	var r1 error
+	if rf, ok := ret.Get(0).(func([]*int) ([]*repository.NotificationSettingsView, error)); ok {
+		return rf(id)
+	}
+	if rf, ok := ret.Get(0).(func([]*int) []*repository.NotificationSettingsView); ok {
+		r0 = rf(id)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*repository.NotificationSettingsView)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func([]*int) error); ok {
+		r1 = rf(id)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// SaveAllNotificationSettings provides a mock function with given fields: notificationSettings, tx
+func (_m *NotificationSettingsRepository) SaveAllNotificationSettings(notificationSettings []repository.NotificationSettings, tx *pg.Tx) (int, error) {
+	ret := _m.Called(notificationSettings, tx)
+
+	var r0 int
+	var r1 error
+	if rf, ok := ret.Get(0).(func([]repository.NotificationSettings, *pg.Tx) (int, error)); ok {
+		return rf(notificationSettings, tx)
+	}
+	if rf, ok := ret.Get(0).(func([]repository.NotificationSettings, *pg.Tx) int); ok {
+		r0 = rf(notificationSettings, tx)
+	} else {
+		r0 = ret.Get(0).(int)
+	}
+
+	if rf, ok := ret.Get(1).(func([]repository.NotificationSettings, *pg.Tx) error); ok {
+		r1 = rf(notificationSettings, tx)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// SaveNotificationSetting provides a mock function with given fields: notificationSettings, tx
+func (_m *NotificationSettingsRepository) SaveNotificationSetting(notificationSettings *repository.NotificationSettings, tx *pg.Tx) (*repository.NotificationSettings, error) {
+	ret := _m.Called(notificationSettings, tx)
+
+	var r0 *repository.NotificationSettings
+	var r1 error
+	if rf, ok := ret.Get(0).(func(*repository.NotificationSettings, *pg.Tx) (*repository.NotificationSettings, error)); ok {
+		return rf(notificationSettings, tx)
+	}
+	if rf, ok := ret.Get(0).(func(*repository.NotificationSettings, *pg.Tx) *repository.NotificationSettings); ok {
+		r0 = rf(notificationSettings, tx)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.NotificationSettings)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(*repository.NotificationSettings, *pg.Tx) error); ok {
+		r1 = rf(notificationSettings, tx)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// SaveNotificationSettingsConfig provides a mock function with given fields: notificationSettingsView, tx
+func (_m *NotificationSettingsRepository) SaveNotificationSettingsConfig(notificationSettingsView *repository.NotificationSettingsView, tx *pg.Tx) (*repository.NotificationSettingsView, error) {
+	ret := _m.Called(notificationSettingsView, tx)
+
+	var r0 *repository.NotificationSettingsView
+	var r1 error
+	if rf, ok := ret.Get(0).(func(*repository.NotificationSettingsView, *pg.Tx) (*repository.NotificationSettingsView, error)); ok {
+		return rf(notificationSettingsView, tx)
+	}
+	if rf, ok := ret.Get(0).(func(*repository.NotificationSettingsView, *pg.Tx) *repository.NotificationSettingsView); ok {
+		r0 = rf(notificationSettingsView, tx)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.NotificationSettingsView)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(*repository.NotificationSettingsView, *pg.Tx) error); ok {
+		r1 = rf(notificationSettingsView, tx)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// UpdateNotificationSettings provides a mock function with given fields: notificationSettings, tx
+func (_m *NotificationSettingsRepository) UpdateNotificationSettings(notificationSettings *repository.NotificationSettings, tx *pg.Tx) (*repository.NotificationSettings, error) {
+	ret := _m.Called(notificationSettings, tx)
+
+	var r0 *repository.NotificationSettings
+	var r1 error
+	if rf, ok := ret.Get(0).(func(*repository.NotificationSettings, *pg.Tx) (*repository.NotificationSettings, error)); ok {
+		return rf(notificationSettings, tx)
+	}
+	if rf, ok := ret.Get(0).(func(*repository.NotificationSettings, *pg.Tx) *repository.NotificationSettings); ok {
+		r0 = rf(notificationSettings, tx)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.NotificationSettings)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(*repository.NotificationSettings, *pg.Tx) error); ok {
+		r1 = rf(notificationSettings, tx)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// UpdateNotificationSettingsView provides a mock function with given fields: notificationSettingsView, tx
+func (_m *NotificationSettingsRepository) UpdateNotificationSettingsView(notificationSettingsView *repository.NotificationSettingsView, tx *pg.Tx) (*repository.NotificationSettingsView, error) {
+	ret := _m.Called(notificationSettingsView, tx)
+
+	var r0 *repository.NotificationSettingsView
+	var r1 error
+	if rf, ok := ret.Get(0).(func(*repository.NotificationSettingsView, *pg.Tx) (*repository.NotificationSettingsView, error)); ok {
+		return rf(notificationSettingsView, tx)
+	}
+	if rf, ok := ret.Get(0).(func(*repository.NotificationSettingsView, *pg.Tx) *repository.NotificationSettingsView); ok {
+		r0 = rf(notificationSettingsView, tx)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.NotificationSettingsView)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(*repository.NotificationSettingsView, *pg.Tx) error); ok {
+		r1 = rf(notificationSettingsView, tx)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+type mockConstructorTestingTNewNotificationSettingsRepository interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewNotificationSettingsRepository creates a new instance of NotificationSettingsRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewNotificationSettingsRepository(t mockConstructorTestingTNewNotificationSettingsRepository) *NotificationSettingsRepository {
+	mock := &NotificationSettingsRepository{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/internal/sql/repository/mocks/WebhookNotificationRepository.go b/internal/sql/repository/mocks/WebhookNotificationRepository.go
new file mode 100644
index 000000000..e48ce9ce1
--- /dev/null
+++ b/internal/sql/repository/mocks/WebhookNotificationRepository.go
@@ -0,0 +1,198 @@
+// Code generated by mockery v2.20.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	repository "github.com/devtron-labs/devtron/internal/sql/repository"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// WebhookNotificationRepository is an autogenerated mock type for the WebhookNotificationRepository type
+type WebhookNotificationRepository struct {
+	mock.Mock
+}
+
+// FindAll provides a mock function with given fields:
+func (_m *WebhookNotificationRepository) FindAll() ([]repository.WebhookConfig, error) {
+	ret := _m.Called()
+
+	var r0 []repository.WebhookConfig
+	var r1 error
+	if rf, ok := ret.Get(0).(func() ([]repository.WebhookConfig, error)); ok {
+		return rf()
+	}
+	if rf, ok := ret.Get(0).(func() []repository.WebhookConfig); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]repository.WebhookConfig)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func() error); ok {
+		r1 = rf()
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindByIds provides a mock function with given fields: ids
+func (_m *WebhookNotificationRepository) FindByIds(ids []*int) ([]*repository.WebhookConfig, error) {
+	ret := _m.Called(ids)
+
+	var r0 []*repository.WebhookConfig
+	var r1 error
+	if rf, ok := ret.Get(0).(func([]*int) ([]*repository.WebhookConfig, error)); ok {
+		return rf(ids)
+	}
+	if rf, ok := ret.Get(0).(func([]*int) []*repository.WebhookConfig); ok {
+		r0 = rf(ids)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*repository.WebhookConfig)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func([]*int) error); ok {
+		r1 = rf(ids)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindByName provides a mock function with given fields: value
+func (_m *WebhookNotificationRepository) FindByName(value string) ([]repository.WebhookConfig, error) {
+	ret := _m.Called(value)
+
+	var r0 []repository.WebhookConfig
+	var r1 error
+	if rf, ok := ret.Get(0).(func(string) ([]repository.WebhookConfig, error)); ok {
+		return rf(value)
+	}
+	if rf, ok := ret.Get(0).(func(string) []repository.WebhookConfig); ok {
+		r0 = rf(value)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]repository.WebhookConfig)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(string) error); ok {
+		r1 = rf(value)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindOne provides a mock function with given fields: id
+func (_m *WebhookNotificationRepository) FindOne(id int) (*repository.WebhookConfig, error) {
+	ret := _m.Called(id)
+
+	var r0 *repository.WebhookConfig
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int) (*repository.WebhookConfig, error)); ok {
+		return rf(id)
+	}
+	if rf, ok := ret.Get(0).(func(int) *repository.WebhookConfig); ok {
+		r0 = rf(id)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.WebhookConfig)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(int) error); ok {
+		r1 = rf(id)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MarkWebhookConfigDeleted provides a mock function with given fields: webhookConfig
+func (_m *WebhookNotificationRepository) MarkWebhookConfigDeleted(webhookConfig *repository.WebhookConfig) error {
+	ret := _m.Called(webhookConfig)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*repository.WebhookConfig) error); ok {
+		r0 = rf(webhookConfig)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// SaveWebhookConfig provides a mock function with given fields: webhookConfig
+func (_m *WebhookNotificationRepository) SaveWebhookConfig(webhookConfig *repository.WebhookConfig) (*repository.WebhookConfig, error) {
+	ret := _m.Called(webhookConfig)
+
+	var r0 *repository.WebhookConfig
+	var r1 error
+	if rf, ok := ret.Get(0).(func(*repository.WebhookConfig) (*repository.WebhookConfig, error)); ok {
+		return rf(webhookConfig)
+	}
+	if rf, ok := ret.Get(0).(func(*repository.WebhookConfig) *repository.WebhookConfig); ok {
+		r0 = rf(webhookConfig)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.WebhookConfig)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(*repository.WebhookConfig) error); ok {
+		r1 = rf(webhookConfig)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// UpdateWebhookConfig provides a mock function with given fields: webhookConfig
+func (_m *WebhookNotificationRepository) UpdateWebhookConfig(webhookConfig *repository.WebhookConfig) (*repository.WebhookConfig, error) {
+	ret := _m.Called(webhookConfig)
+
+	var r0 *repository.WebhookConfig
+	var r1 error
+	if rf, ok := ret.Get(0).(func(*repository.WebhookConfig) (*repository.WebhookConfig, error)); ok {
+		return rf(webhookConfig)
+	}
+	if rf, ok := ret.Get(0).(func(*repository.WebhookConfig) *repository.WebhookConfig); ok {
+		r0 = rf(webhookConfig)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.WebhookConfig)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(*repository.WebhookConfig) error); ok {
+		r1 = rf(webhookConfig)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+type mockConstructorTestingTNewWebhookNotificationRepository interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewWebhookNotificationRepository creates a new instance of WebhookNotificationRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewWebhookNotificationRepository(t mockConstructorTestingTNewWebhookNotificationRepository) *WebhookNotificationRepository {
+	mock := &WebhookNotificationRepository{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/pkg/appStore/deployment/service/AppStoreDeploymentService.go b/pkg/appStore/deployment/service/AppStoreDeploymentService.go
index 3ee301a98..0de5985df 100644
--- a/pkg/appStore/deployment/service/AppStoreDeploymentService.go
+++ b/pkg/appStore/deployment/service/AppStoreDeploymentService.go
@@ -221,7 +221,13 @@ func (impl AppStoreDeploymentServiceImpl) AppStoreDeployOperationDB(installAppVe
 	//		installedAppModel.DeploymentAppType = util.PIPELINE_DEPLOYMENT_TYPE_HELM
 	//	}
 	//}
-
+	if !isInternalUse {
+		if isGitOpsConfigured && appInstallationMode == util2.SERVER_MODE_FULL {
+			installAppVersionRequest.DeploymentAppType = util.PIPELINE_DEPLOYMENT_TYPE_ACD
+		} else {
+			installAppVersionRequest.DeploymentAppType = util.PIPELINE_DEPLOYMENT_TYPE_HELM
+		}
+	}
 	if installAppVersionRequest.DeploymentAppType == "" {
 		if isGitOpsConfigured {
 			installAppVersionRequest.DeploymentAppType = util.PIPELINE_DEPLOYMENT_TYPE_ACD
diff --git a/pkg/notifier/NotificationConfigService.go b/pkg/notifier/NotificationConfigService.go
index 20e50fc57..c78b3184f 100644
--- a/pkg/notifier/NotificationConfigService.go
+++ b/pkg/notifier/NotificationConfigService.go
@@ -51,6 +51,7 @@ type NotificationConfigServiceImpl struct {
 	ciPipelineRepository           pipelineConfig.CiPipelineRepository
 	pipelineRepository             pipelineConfig.PipelineRepository
 	slackRepository                repository.SlackNotificationRepository
+	webhookRepository              repository.WebhookNotificationRepository
 	sesRepository                  repository.SESNotificationRepository
 	smtpRepository                 repository.SMTPNotificationRepository
 	teamRepository                 repository2.TeamRepository
@@ -163,7 +164,7 @@ type ProvidersConfig struct {
 }
 
 func NewNotificationConfigServiceImpl(logger *zap.SugaredLogger, notificationSettingsRepository repository.NotificationSettingsRepository, notificationConfigBuilder NotificationConfigBuilder, ciPipelineRepository pipelineConfig.CiPipelineRepository,
-	pipelineRepository pipelineConfig.PipelineRepository, slackRepository repository.SlackNotificationRepository,
+	pipelineRepository pipelineConfig.PipelineRepository, slackRepository repository.SlackNotificationRepository, webhookRepository repository.WebhookNotificationRepository,
 	sesRepository repository.SESNotificationRepository, smtpRepository repository.SMTPNotificationRepository,
 	teamRepository repository2.TeamRepository,
 	environmentRepository repository3.EnvironmentRepository, appRepository app.AppRepository,
@@ -176,6 +177,7 @@ func NewNotificationConfigServiceImpl(logger *zap.SugaredLogger, notificationSet
 		ciPipelineRepository:           ciPipelineRepository,
 		sesRepository:                  sesRepository,
 		slackRepository:                slackRepository,
+		webhookRepository:              webhookRepository,
 		smtpRepository:                 smtpRepository,
 		teamRepository:                 teamRepository,
 		environmentRepository:          environmentRepository,
@@ -365,6 +367,7 @@ func (impl *NotificationConfigServiceImpl) BuildNotificationSettingsResponse(not
 
 		if config.Providers != nil && len(config.Providers) > 0 {
 			var slackIds []*int
+			var webhookIds []*int
 			var sesUserIds []int32
 			var smtpUserIds []int32
 			var providerConfigs []*ProvidersConfig
@@ -377,6 +380,8 @@ func (impl *NotificationConfigServiceImpl) BuildNotificationSettingsResponse(not
 						sesUserIds = append(sesUserIds, int32(item.ConfigId))
 					} else if item.Destination == util.SMTP {
 						smtpUserIds = append(smtpUserIds, int32(item.ConfigId))
+					} else if item.Destination == util.Webhook {
+						webhookIds = append(webhookIds, &item.ConfigId)
 					}
 				} else {
 					providerConfigs = append(providerConfigs, &ProvidersConfig{Dest: string(item.Destination), Recipient: item.Recipient})
@@ -392,6 +397,16 @@ func (impl *NotificationConfigServiceImpl) BuildNotificationSettingsResponse(not
 					providerConfigs = append(providerConfigs, &ProvidersConfig{Id: item.Id, ConfigName: item.ConfigName, Dest: string(util.Slack)})
 				}
 			}
+			if len(webhookIds) > 0 {
+				webhookConfigs, err := impl.webhookRepository.FindByIds(webhookIds)
+				if err != nil && err != pg.ErrNoRows {
+					impl.logger.Errorw("error in fetching webhook config", "err", err)
+					return notificationSettingsResponses, deletedItemCount, err
+				}
+				for _, item := range webhookConfigs {
+					providerConfigs = append(providerConfigs, &ProvidersConfig{Id: item.Id, ConfigName: item.ConfigName, Dest: string(util.Webhook)})
+				}
+			}
 
 			if len(sesUserIds) > 0 {
 				sesConfigs, err := impl.userRepository.GetByIds(sesUserIds)
diff --git a/pkg/notifier/SlackNotificationService.go b/pkg/notifier/SlackNotificationService.go
index db2c49d88..d891e98dc 100644
--- a/pkg/notifier/SlackNotificationService.go
+++ b/pkg/notifier/SlackNotificationService.go
@@ -33,6 +33,8 @@ import (
 )
 
 const SLACK_CONFIG_TYPE = "slack"
+const SLACK_URL = "https://hooks.slack.com/"
+const WEBHOOK_URL = "https://"
 
 type SlackNotificationService interface {
 	SaveOrEditNotificationConfig(channelReq []SlackConfigDto, userId int32) ([]int, error)
@@ -47,6 +49,7 @@ type SlackNotificationServiceImpl struct {
 	logger                         *zap.SugaredLogger
 	teamService                    team.TeamService
 	slackRepository                repository.SlackNotificationRepository
+	webhookRepository              repository.WebhookNotificationRepository
 	userRepository                 repository2.UserRepository
 	notificationSettingsRepository repository.NotificationSettingsRepository
 }
@@ -65,12 +68,13 @@ type SlackConfigDto struct {
 	Id          int    `json:"id" validate:"number"`
 }
 
-func NewSlackNotificationServiceImpl(logger *zap.SugaredLogger, slackRepository repository.SlackNotificationRepository, teamService team.TeamService,
+func NewSlackNotificationServiceImpl(logger *zap.SugaredLogger, slackRepository repository.SlackNotificationRepository, webhookRepository repository.WebhookNotificationRepository, teamService team.TeamService,
 	userRepository repository2.UserRepository, notificationSettingsRepository repository.NotificationSettingsRepository) *SlackNotificationServiceImpl {
 	return &SlackNotificationServiceImpl{
 		logger:                         logger,
 		teamService:                    teamService,
 		slackRepository:                slackRepository,
+		webhookRepository:              webhookRepository,
 		userRepository:                 userRepository,
 		notificationSettingsRepository: notificationSettingsRepository,
 	}
@@ -202,19 +206,31 @@ func (impl *SlackNotificationServiceImpl) buildConfigUpdateModel(slackConfig *re
 func (impl *SlackNotificationServiceImpl) RecipientListingSuggestion(value string) ([]*NotificationRecipientListingResponse, error) {
 	var results []*NotificationRecipientListingResponse
 
-	sesConfigs, err := impl.slackRepository.FindByName(value)
+	slackConfigs, err := impl.slackRepository.FindByName(value)
 	if err != nil && !util.IsErrNoRows(err) {
 		impl.logger.Errorw("cannot find all slack config", "err", err)
 		return []*NotificationRecipientListingResponse{}, err
 	}
-	for _, sesConfig := range sesConfigs {
+	for _, slackConfig := range slackConfigs {
 		result := &NotificationRecipientListingResponse{
-			ConfigId:  sesConfig.Id,
-			Recipient: sesConfig.ConfigName,
+			ConfigId:  slackConfig.Id,
+			Recipient: slackConfig.ConfigName,
 			Dest:      util2.Slack}
 		results = append(results, result)
 	}
+	webhookConfigs, err := impl.webhookRepository.FindByName(value)
 
+	if err != nil && !util.IsErrNoRows(err) {
+		impl.logger.Errorw("cannot find all webhook config", "err", err)
+		return []*NotificationRecipientListingResponse{}, err
+	}
+	for _, webhookConfig := range webhookConfigs {
+		result := &NotificationRecipientListingResponse{
+			ConfigId:  webhookConfig.Id,
+			Recipient: webhookConfig.ConfigName,
+			Dest:      util2.Webhook}
+		results = append(results, result)
+	}
 	userList, err := impl.userRepository.FetchUserMatchesByEmailIdExcludingApiTokenUser(value)
 	if err != nil && !util.IsErrNoRows(err) {
 		impl.logger.Errorw("cannot find all slack config", "err", err)
@@ -250,8 +266,10 @@ func (impl *SlackNotificationServiceImpl) RecipientListingSuggestion(value strin
 								result := &NotificationRecipientListingResponse{
 									Recipient: v.(string),
 								}
-								if strings.Contains(v.(string), "https://") {
+								if strings.Contains(v.(string), SLACK_URL) {
 									result.Dest = util2.Slack
+								} else if strings.Contains(v.(string), WEBHOOK_URL) {
+									result.Dest = util2.Webhook
 								} else {
 									result.Dest = util2.SES
 								}
diff --git a/pkg/notifier/WebhookNotificationService.go b/pkg/notifier/WebhookNotificationService.go
new file mode 100644
index 000000000..7c6b0066d
--- /dev/null
+++ b/pkg/notifier/WebhookNotificationService.go
@@ -0,0 +1,236 @@
+package notifier
+
+import (
+	"fmt"
+	"github.com/devtron-labs/devtron/internal/sql/repository"
+	"github.com/devtron-labs/devtron/internal/util"
+	"github.com/devtron-labs/devtron/pkg/sql"
+	"github.com/devtron-labs/devtron/pkg/team"
+	repository2 "github.com/devtron-labs/devtron/pkg/user/repository"
+	util2 "github.com/devtron-labs/devtron/util/event"
+	"github.com/go-pg/pg"
+	"go.uber.org/zap"
+	"time"
+)
+
+const WEBHOOK_CONFIG_TYPE = "webhook"
+
+type WebhookVariable string
+
+const (
+	// these fields will be configurable in future
+	DevtronContainerImageTag WebhookVariable = "{{devtronContainerImageTag}}"
+	DevtronAppName           WebhookVariable = "{{devtronAppName}}"
+	DevtronAppId             WebhookVariable = "{{devtronAppId}}"
+	DevtronEnvName           WebhookVariable = "{{devtronEnvName}}"
+	DevtronEnvId             WebhookVariable = "{{devtronEnvId}}"
+	DevtronCiPipelineId      WebhookVariable = "{{devtronCiPipelineId}}"
+	DevtronCdPipelineId      WebhookVariable = "{{devtronCdPipelineId}}"
+	DevtronTriggeredByEmail  WebhookVariable = "{{devtronTriggeredByEmail}}"
+	EventType                WebhookVariable = "{{eventType}}"
+)
+
+type WebhookNotificationService interface {
+	SaveOrEditNotificationConfig(channelReq []WebhookConfigDto, userId int32) ([]int, error)
+	FetchWebhookNotificationConfigById(id int) (*WebhookConfigDto, error)
+	GetWebhookVariables() (map[string]WebhookVariable, error)
+	FetchAllWebhookNotificationConfig() ([]*WebhookConfigDto, error)
+	FetchAllWebhookNotificationConfigAutocomplete() ([]*NotificationChannelAutoResponse, error)
+	DeleteNotificationConfig(deleteReq *WebhookConfigDto, userId int32) error
+}
+
+type WebhookNotificationServiceImpl struct {
+	logger                         *zap.SugaredLogger
+	webhookRepository              repository.WebhookNotificationRepository
+	teamService                    team.TeamService
+	userRepository                 repository2.UserRepository
+	notificationSettingsRepository repository.NotificationSettingsRepository
+}
+type WebhookChannelConfig struct {
+	Channel           util2.Channel       `json:"channel" validate:"required"`
+	WebhookConfigDtos *[]WebhookConfigDto `json:"configs"`
+}
+
+type WebhookConfigDto struct {
+	OwnerId     int32                  `json:"userId" validate:"number"`
+	WebhookUrl  string                 `json:"webhookUrl" validate:"required"`
+	ConfigName  string                 `json:"configName" validate:"required"`
+	Header      map[string]interface{} `json:"header"`
+	Payload     map[string]interface{} `json:"payload"`
+	Description string                 `json:"description"`
+	Id          int                    `json:"id" validate:"number"`
+}
+
+func NewWebhookNotificationServiceImpl(logger *zap.SugaredLogger, webhookRepository repository.WebhookNotificationRepository, teamService team.TeamService,
+	userRepository repository2.UserRepository, notificationSettingsRepository repository.NotificationSettingsRepository) *WebhookNotificationServiceImpl {
+	return &WebhookNotificationServiceImpl{
+		logger:                         logger,
+		webhookRepository:              webhookRepository,
+		teamService:                    teamService,
+		userRepository:                 userRepository,
+		notificationSettingsRepository: notificationSettingsRepository,
+	}
+}
+
+func (impl *WebhookNotificationServiceImpl) SaveOrEditNotificationConfig(channelReq []WebhookConfigDto, userId int32) ([]int, error) {
+	var responseIds []int
+	webhookConfigs := buildWebhookNewConfigs(channelReq, userId)
+	for _, config := range webhookConfigs {
+		if config.Id != 0 {
+			model, err := impl.webhookRepository.FindOne(config.Id)
+			if err != nil && !util.IsErrNoRows(err) {
+				impl.logger.Errorw("err while fetching webhook config", "err", err)
+				return []int{}, err
+			}
+			impl.buildConfigUpdateModel(config, model, userId)
+			model, uErr := impl.webhookRepository.UpdateWebhookConfig(model)
+			if uErr != nil {
+				impl.logger.Errorw("err while updating webhook config", "err", err)
+				return []int{}, uErr
+			}
+		} else {
+			_, iErr := impl.webhookRepository.SaveWebhookConfig(config)
+			if iErr != nil {
+				impl.logger.Errorw("err while inserting webhook config", "err", iErr)
+				return []int{}, iErr
+			}
+
+		}
+		responseIds = append(responseIds, config.Id)
+	}
+	return responseIds, nil
+}
+
+func (impl *WebhookNotificationServiceImpl) FetchWebhookNotificationConfigById(id int) (*WebhookConfigDto, error) {
+	webhookConfig, err := impl.webhookRepository.FindOne(id)
+	if err != nil && !util.IsErrNoRows(err) {
+		impl.logger.Errorw("cannot find all webhoook config", "err", err)
+		return nil, err
+	}
+	webhoookConfigDto := impl.adaptWebhookConfig(*webhookConfig)
+	return &webhoookConfigDto, nil
+}
+
+func (impl *WebhookNotificationServiceImpl) GetWebhookVariables() (map[string]WebhookVariable, error) {
+	variables := map[string]WebhookVariable{
+		"devtronContainerImageTag": DevtronContainerImageTag,
+		"devtronAppName":           DevtronAppName,
+		"devtronAppId":             DevtronAppId,
+		"devtronEnvName":           DevtronEnvName,
+		"devtronEnvId":             DevtronEnvId,
+		"devtronCiPipelineId":      DevtronCiPipelineId,
+		"devtronCdPipelineId":      DevtronCdPipelineId,
+		"devtronTriggeredByEmail":  DevtronTriggeredByEmail,
+		"eventType":                EventType,
+	}
+
+	return variables, nil
+}
+
+func (impl *WebhookNotificationServiceImpl) FetchAllWebhookNotificationConfig() ([]*WebhookConfigDto, error) {
+	var responseDto []*WebhookConfigDto
+	webhookConfigs, err := impl.webhookRepository.FindAll()
+	if err != nil && !util.IsErrNoRows(err) {
+		impl.logger.Errorw("cannot find all webhoook config", "err", err)
+		return []*WebhookConfigDto{}, err
+	}
+	for _, webhookConfig := range webhookConfigs {
+		webhookConfigDto := impl.adaptWebhookConfig(webhookConfig)
+		responseDto = append(responseDto, &webhookConfigDto)
+	}
+	if responseDto == nil {
+		responseDto = make([]*WebhookConfigDto, 0)
+	}
+	return responseDto, nil
+}
+
+func (impl *WebhookNotificationServiceImpl) FetchAllWebhookNotificationConfigAutocomplete() ([]*NotificationChannelAutoResponse, error) {
+	var responseDto []*NotificationChannelAutoResponse
+	webhookConfigs, err := impl.webhookRepository.FindAll()
+	if err != nil && !util.IsErrNoRows(err) {
+		impl.logger.Errorw("cannot find all webhoook config", "err", err)
+		return []*NotificationChannelAutoResponse{}, err
+	}
+	for _, webhookConfig := range webhookConfigs {
+		webhookConfigDto := &NotificationChannelAutoResponse{
+			Id:         webhookConfig.Id,
+			ConfigName: webhookConfig.ConfigName,
+		}
+		responseDto = append(responseDto, webhookConfigDto)
+	}
+	return responseDto, nil
+}
+
+func (impl *WebhookNotificationServiceImpl) adaptWebhookConfig(webhookConfig repository.WebhookConfig) WebhookConfigDto {
+	webhookConfigDto := WebhookConfigDto{
+		OwnerId:     webhookConfig.OwnerId,
+		WebhookUrl:  webhookConfig.WebHookUrl,
+		ConfigName:  webhookConfig.ConfigName,
+		Header:      webhookConfig.Header,
+		Payload:     webhookConfig.Payload,
+		Description: webhookConfig.Description,
+		Id:          webhookConfig.Id,
+	}
+	return webhookConfigDto
+}
+
+func buildWebhookNewConfigs(webhookReq []WebhookConfigDto, userId int32) []*repository.WebhookConfig {
+	var webhookConfigs []*repository.WebhookConfig
+	for _, c := range webhookReq {
+		webhookConfig := &repository.WebhookConfig{
+			Id:          c.Id,
+			ConfigName:  c.ConfigName,
+			WebHookUrl:  c.WebhookUrl,
+			Header:      c.Header,
+			Payload:     c.Payload,
+			Description: c.Description,
+			AuditLog: sql.AuditLog{
+				CreatedBy: userId,
+				CreatedOn: time.Now(),
+				UpdatedOn: time.Now(),
+				UpdatedBy: userId,
+			},
+		}
+		webhookConfig.OwnerId = userId
+		webhookConfigs = append(webhookConfigs, webhookConfig)
+	}
+	return webhookConfigs
+}
+
+func (impl *WebhookNotificationServiceImpl) buildConfigUpdateModel(webhookConfig *repository.WebhookConfig, model *repository.WebhookConfig, userId int32) {
+	model.WebHookUrl = webhookConfig.WebHookUrl
+	model.ConfigName = webhookConfig.ConfigName
+	model.Description = webhookConfig.Description
+	model.Payload = webhookConfig.Payload
+	model.Header = webhookConfig.Header
+	model.OwnerId = webhookConfig.OwnerId
+	model.UpdatedOn = time.Now()
+	model.UpdatedBy = userId
+}
+
+func (impl *WebhookNotificationServiceImpl) DeleteNotificationConfig(deleteReq *WebhookConfigDto, userId int32) error {
+	existingConfig, err := impl.webhookRepository.FindOne(deleteReq.Id)
+	if err != nil {
+		impl.logger.Errorw("No matching entry found for delete", "err", err, "id", deleteReq.Id)
+		return err
+	}
+	notifications, err := impl.notificationSettingsRepository.FindNotificationSettingsByConfigIdAndConfigType(deleteReq.Id, WEBHOOK_CONFIG_TYPE)
+	if err != nil && err != pg.ErrNoRows {
+		impl.logger.Errorw("error in deleting webhook config", "config", deleteReq)
+		return err
+	}
+	if len(notifications) > 0 {
+		impl.logger.Errorw("found notifications using this config, cannot delete", "config", deleteReq)
+		return fmt.Errorf(" Please delete all notifications using this config before deleting")
+	}
+
+	existingConfig.UpdatedOn = time.Now()
+	existingConfig.UpdatedBy = userId
+	//deleting webhook config
+	err = impl.webhookRepository.MarkWebhookConfigDeleted(existingConfig)
+	if err != nil {
+		impl.logger.Errorw("error in deleting webhook config", "err", err, "id", existingConfig.Id)
+		return err
+	}
+	return nil
+}
diff --git a/pkg/pipeline/PipelineBuilder.go b/pkg/pipeline/PipelineBuilder.go
index 4594634a6..b3a170550 100644
--- a/pkg/pipeline/PipelineBuilder.go
+++ b/pkg/pipeline/PipelineBuilder.go
@@ -1777,6 +1777,13 @@ func (impl PipelineBuilderImpl) SetPipelineDeploymentAppType(pipelineCreateReque
 	//}
 
 	for _, pipeline := range pipelineCreateRequest.Pipelines {
+		if !impl.deploymentConfig.IsInternalUse {
+			if isGitOpsConfigured {
+				pipeline.DeploymentAppType = util.PIPELINE_DEPLOYMENT_TYPE_ACD
+			} else {
+				pipeline.DeploymentAppType = util.PIPELINE_DEPLOYMENT_TYPE_HELM
+			}
+		}
 		if pipeline.DeploymentAppType == "" {
 			if isGitOpsConfigured {
 				pipeline.DeploymentAppType = util.PIPELINE_DEPLOYMENT_TYPE_ACD
diff --git a/pkg/team/mocks/TeamService.go b/pkg/team/mocks/TeamService.go
new file mode 100644
index 000000000..1c72cb8ed
--- /dev/null
+++ b/pkg/team/mocks/TeamService.go
@@ -0,0 +1,224 @@
+// Code generated by mockery v2.20.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	team "github.com/devtron-labs/devtron/pkg/team"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// TeamService is an autogenerated mock type for the TeamService type
+type TeamService struct {
+	mock.Mock
+}
+
+// Create provides a mock function with given fields: request
+func (_m *TeamService) Create(request *team.TeamRequest) (*team.TeamRequest, error) {
+	ret := _m.Called(request)
+
+	var r0 *team.TeamRequest
+	var r1 error
+	if rf, ok := ret.Get(0).(func(*team.TeamRequest) (*team.TeamRequest, error)); ok {
+		return rf(request)
+	}
+	if rf, ok := ret.Get(0).(func(*team.TeamRequest) *team.TeamRequest); ok {
+		r0 = rf(request)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*team.TeamRequest)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(*team.TeamRequest) error); ok {
+		r1 = rf(request)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Delete provides a mock function with given fields: request
+func (_m *TeamService) Delete(request *team.TeamRequest) error {
+	ret := _m.Called(request)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*team.TeamRequest) error); ok {
+		r0 = rf(request)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// FetchAllActive provides a mock function with given fields:
+func (_m *TeamService) FetchAllActive() ([]team.TeamRequest, error) {
+	ret := _m.Called()
+
+	var r0 []team.TeamRequest
+	var r1 error
+	if rf, ok := ret.Get(0).(func() ([]team.TeamRequest, error)); ok {
+		return rf()
+	}
+	if rf, ok := ret.Get(0).(func() []team.TeamRequest); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]team.TeamRequest)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func() error); ok {
+		r1 = rf()
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FetchForAutocomplete provides a mock function with given fields:
+func (_m *TeamService) FetchForAutocomplete() ([]team.TeamRequest, error) {
+	ret := _m.Called()
+
+	var r0 []team.TeamRequest
+	var r1 error
+	if rf, ok := ret.Get(0).(func() ([]team.TeamRequest, error)); ok {
+		return rf()
+	}
+	if rf, ok := ret.Get(0).(func() []team.TeamRequest); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]team.TeamRequest)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func() error); ok {
+		r1 = rf()
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FetchOne provides a mock function with given fields: id
+func (_m *TeamService) FetchOne(id int) (*team.TeamRequest, error) {
+	ret := _m.Called(id)
+
+	var r0 *team.TeamRequest
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int) (*team.TeamRequest, error)); ok {
+		return rf(id)
+	}
+	if rf, ok := ret.Get(0).(func(int) *team.TeamRequest); ok {
+		r0 = rf(id)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*team.TeamRequest)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(int) error); ok {
+		r1 = rf(id)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindByIds provides a mock function with given fields: ids
+func (_m *TeamService) FindByIds(ids []*int) ([]*team.TeamBean, error) {
+	ret := _m.Called(ids)
+
+	var r0 []*team.TeamBean
+	var r1 error
+	if rf, ok := ret.Get(0).(func([]*int) ([]*team.TeamBean, error)); ok {
+		return rf(ids)
+	}
+	if rf, ok := ret.Get(0).(func([]*int) []*team.TeamBean); ok {
+		r0 = rf(ids)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*team.TeamBean)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func([]*int) error); ok {
+		r1 = rf(ids)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FindByTeamName provides a mock function with given fields: teamName
+func (_m *TeamService) FindByTeamName(teamName string) (*team.TeamRequest, error) {
+	ret := _m.Called(teamName)
+
+	var r0 *team.TeamRequest
+	var r1 error
+	if rf, ok := ret.Get(0).(func(string) (*team.TeamRequest, error)); ok {
+		return rf(teamName)
+	}
+	if rf, ok := ret.Get(0).(func(string) *team.TeamRequest); ok {
+		r0 = rf(teamName)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*team.TeamRequest)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(string) error); ok {
+		r1 = rf(teamName)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Update provides a mock function with given fields: request
+func (_m *TeamService) Update(request *team.TeamRequest) (*team.TeamRequest, error) {
+	ret := _m.Called(request)
+
+	var r0 *team.TeamRequest
+	var r1 error
+	if rf, ok := ret.Get(0).(func(*team.TeamRequest) (*team.TeamRequest, error)); ok {
+		return rf(request)
+	}
+	if rf, ok := ret.Get(0).(func(*team.TeamRequest) *team.TeamRequest); ok {
+		r0 = rf(request)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*team.TeamRequest)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(*team.TeamRequest) error); ok {
+		r1 = rf(request)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+type mockConstructorTestingTNewTeamService interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewTeamService creates a new instance of TeamService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewTeamService(t mockConstructorTestingTNewTeamService) *TeamService {
+	mock := &TeamService{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/pkg/user/repository/mocks/UserRepository.go b/pkg/user/repository/mocks/UserRepository.go
new file mode 100644
index 000000000..326e421ba
--- /dev/null
+++ b/pkg/user/repository/mocks/UserRepository.go
@@ -0,0 +1,330 @@
+// Code generated by mockery v2.20.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	bean "github.com/devtron-labs/devtron/api/bean"
+	mock "github.com/stretchr/testify/mock"
+
+	pg "github.com/go-pg/pg"
+
+	repository "github.com/devtron-labs/devtron/pkg/user/repository"
+)
+
+// UserRepository is an autogenerated mock type for the UserRepository type
+type UserRepository struct {
+	mock.Mock
+}
+
+// CreateUser provides a mock function with given fields: userModel, tx
+func (_m *UserRepository) CreateUser(userModel *repository.UserModel, tx *pg.Tx) (*repository.UserModel, error) {
+	ret := _m.Called(userModel, tx)
+
+	var r0 *repository.UserModel
+	var r1 error
+	if rf, ok := ret.Get(0).(func(*repository.UserModel, *pg.Tx) (*repository.UserModel, error)); ok {
+		return rf(userModel, tx)
+	}
+	if rf, ok := ret.Get(0).(func(*repository.UserModel, *pg.Tx) *repository.UserModel); ok {
+		r0 = rf(userModel, tx)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.UserModel)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(*repository.UserModel, *pg.Tx) error); ok {
+		r1 = rf(userModel, tx)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FetchActiveOrDeletedUserByEmail provides a mock function with given fields: email
+func (_m *UserRepository) FetchActiveOrDeletedUserByEmail(email string) (*repository.UserModel, error) {
+	ret := _m.Called(email)
+
+	var r0 *repository.UserModel
+	var r1 error
+	if rf, ok := ret.Get(0).(func(string) (*repository.UserModel, error)); ok {
+		return rf(email)
+	}
+	if rf, ok := ret.Get(0).(func(string) *repository.UserModel); ok {
+		r0 = rf(email)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.UserModel)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(string) error); ok {
+		r1 = rf(email)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FetchActiveUserByEmail provides a mock function with given fields: email
+func (_m *UserRepository) FetchActiveUserByEmail(email string) (bean.UserInfo, error) {
+	ret := _m.Called(email)
+
+	var r0 bean.UserInfo
+	var r1 error
+	if rf, ok := ret.Get(0).(func(string) (bean.UserInfo, error)); ok {
+		return rf(email)
+	}
+	if rf, ok := ret.Get(0).(func(string) bean.UserInfo); ok {
+		r0 = rf(email)
+	} else {
+		r0 = ret.Get(0).(bean.UserInfo)
+	}
+
+	if rf, ok := ret.Get(1).(func(string) error); ok {
+		r1 = rf(email)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FetchUserDetailByEmail provides a mock function with given fields: email
+func (_m *UserRepository) FetchUserDetailByEmail(email string) (bean.UserInfo, error) {
+	ret := _m.Called(email)
+
+	var r0 bean.UserInfo
+	var r1 error
+	if rf, ok := ret.Get(0).(func(string) (bean.UserInfo, error)); ok {
+		return rf(email)
+	}
+	if rf, ok := ret.Get(0).(func(string) bean.UserInfo); ok {
+		r0 = rf(email)
+	} else {
+		r0 = ret.Get(0).(bean.UserInfo)
+	}
+
+	if rf, ok := ret.Get(1).(func(string) error); ok {
+		r1 = rf(email)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// FetchUserMatchesByEmailIdExcludingApiTokenUser provides a mock function with given fields: email
+func (_m *UserRepository) FetchUserMatchesByEmailIdExcludingApiTokenUser(email string) ([]repository.UserModel, error) {
+	ret := _m.Called(email)
+
+	var r0 []repository.UserModel
+	var r1 error
+	if rf, ok := ret.Get(0).(func(string) ([]repository.UserModel, error)); ok {
+		return rf(email)
+	}
+	if rf, ok := ret.Get(0).(func(string) []repository.UserModel); ok {
+		r0 = rf(email)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]repository.UserModel)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(string) error); ok {
+		r1 = rf(email)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetAllExcludingApiTokenUser provides a mock function with given fields:
+func (_m *UserRepository) GetAllExcludingApiTokenUser() ([]repository.UserModel, error) {
+	ret := _m.Called()
+
+	var r0 []repository.UserModel
+	var r1 error
+	if rf, ok := ret.Get(0).(func() ([]repository.UserModel, error)); ok {
+		return rf()
+	}
+	if rf, ok := ret.Get(0).(func() []repository.UserModel); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]repository.UserModel)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func() error); ok {
+		r1 = rf()
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetById provides a mock function with given fields: id
+func (_m *UserRepository) GetById(id int32) (*repository.UserModel, error) {
+	ret := _m.Called(id)
+
+	var r0 *repository.UserModel
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int32) (*repository.UserModel, error)); ok {
+		return rf(id)
+	}
+	if rf, ok := ret.Get(0).(func(int32) *repository.UserModel); ok {
+		r0 = rf(id)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.UserModel)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(int32) error); ok {
+		r1 = rf(id)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetByIdIncludeDeleted provides a mock function with given fields: id
+func (_m *UserRepository) GetByIdIncludeDeleted(id int32) (*repository.UserModel, error) {
+	ret := _m.Called(id)
+
+	var r0 *repository.UserModel
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int32) (*repository.UserModel, error)); ok {
+		return rf(id)
+	}
+	if rf, ok := ret.Get(0).(func(int32) *repository.UserModel); ok {
+		r0 = rf(id)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.UserModel)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(int32) error); ok {
+		r1 = rf(id)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetByIds provides a mock function with given fields: ids
+func (_m *UserRepository) GetByIds(ids []int32) ([]repository.UserModel, error) {
+	ret := _m.Called(ids)
+
+	var r0 []repository.UserModel
+	var r1 error
+	if rf, ok := ret.Get(0).(func([]int32) ([]repository.UserModel, error)); ok {
+		return rf(ids)
+	}
+	if rf, ok := ret.Get(0).(func([]int32) []repository.UserModel); ok {
+		r0 = rf(ids)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]repository.UserModel)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func([]int32) error); ok {
+		r1 = rf(ids)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetConnection provides a mock function with given fields:
+func (_m *UserRepository) GetConnection() *pg.DB {
+	ret := _m.Called()
+
+	var r0 *pg.DB
+	if rf, ok := ret.Get(0).(func() *pg.DB); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*pg.DB)
+		}
+	}
+
+	return r0
+}
+
+// UpdateRoleIdForUserRolesMappings provides a mock function with given fields: roleId, newRoleId
+func (_m *UserRepository) UpdateRoleIdForUserRolesMappings(roleId int, newRoleId int) (*repository.UserRoleModel, error) {
+	ret := _m.Called(roleId, newRoleId)
+
+	var r0 *repository.UserRoleModel
+	var r1 error
+	if rf, ok := ret.Get(0).(func(int, int) (*repository.UserRoleModel, error)); ok {
+		return rf(roleId, newRoleId)
+	}
+	if rf, ok := ret.Get(0).(func(int, int) *repository.UserRoleModel); ok {
+		r0 = rf(roleId, newRoleId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.UserRoleModel)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(int, int) error); ok {
+		r1 = rf(roleId, newRoleId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// UpdateUser provides a mock function with given fields: userModel, tx
+func (_m *UserRepository) UpdateUser(userModel *repository.UserModel, tx *pg.Tx) (*repository.UserModel, error) {
+	ret := _m.Called(userModel, tx)
+
+	var r0 *repository.UserModel
+	var r1 error
+	if rf, ok := ret.Get(0).(func(*repository.UserModel, *pg.Tx) (*repository.UserModel, error)); ok {
+		return rf(userModel, tx)
+	}
+	if rf, ok := ret.Get(0).(func(*repository.UserModel, *pg.Tx) *repository.UserModel); ok {
+		r0 = rf(userModel, tx)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*repository.UserModel)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(*repository.UserModel, *pg.Tx) error); ok {
+		r1 = rf(userModel, tx)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+type mockConstructorTestingTNewUserRepository interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewUserRepository creates a new instance of UserRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewUserRepository(t mockConstructorTestingTNewUserRepository) *UserRepository {
+	mock := &UserRepository{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/scripts/sql/151_webhook_config_notification.down.sql b/scripts/sql/151_webhook_config_notification.down.sql
new file mode 100644
index 000000000..fe8c0c0d7
--- /dev/null
+++ b/scripts/sql/151_webhook_config_notification.down.sql
@@ -0,0 +1,2 @@
+DROP TABLE IF EXISTS "public"."webhook_config";
+DROP SEQUENCE IF EXISTS public.id_seq_webhook_config;
\ No newline at end of file
diff --git a/scripts/sql/151_webhook_config_notification.up.sql b/scripts/sql/151_webhook_config_notification.up.sql
new file mode 100644
index 000000000..5dccbad8e
--- /dev/null
+++ b/scripts/sql/151_webhook_config_notification.up.sql
@@ -0,0 +1,18 @@
+CREATE SEQUENCE IF NOT EXISTS id_seq_webhook_config;
+
+CREATE TABLE "public"."webhook_config" (
+   "id" integer NOT NULL DEFAULT nextval('id_seq_webhook_config'::regclass),
+   "web_hook_url"  VARCHAR(100),
+   "config_name"  VARCHAR(100),
+   "header"     jsonb,
+   "payload"     jsonb,
+   "description" text,
+   "owner_id"    integer,
+   "active"      bool,
+   "deleted"     bool NOT NULL DEFAULT FALSE,
+   "created_on" timestamptz,
+   "created_by" int4,
+   "updated_on" timestamptz,
+   "updated_by" int4,
+   PRIMARY KEY (id)
+);
\ No newline at end of file
diff --git a/util/ValidatorHelper.go b/util/ValidatorHelper.go
index df6d484fd..a9336bf11 100644
--- a/util/ValidatorHelper.go
+++ b/util/ValidatorHelper.go
@@ -232,7 +232,7 @@ func AutoScale(dat map[string]interface{}) (bool, error) {
 	if dat["kedaAutoscaling"] != nil {
 		kedaAutoScale, ok := dat["kedaAutoscaling"].(map[string]interface{})["enabled"]
 		if ok {
-			kedaAutoScaleEnabled=kedaAutoScale.(bool)
+			kedaAutoScaleEnabled = kedaAutoScale.(bool)
 		}
 	}
 	if dat["autoscaling"] != nil {
diff --git a/util/event/Event.go b/util/event/Event.go
index 8e8a0bb16..e8f83ede9 100644
--- a/util/event/Event.go
+++ b/util/event/Event.go
@@ -33,9 +33,10 @@ type Level string
 type Channel string
 
 const (
-	Slack Channel = "slack"
-	SES   Channel = "ses"
-	SMTP  Channel = "smtp"
+	Slack   Channel = "slack"
+	SES     Channel = "ses"
+	SMTP    Channel = "smtp"
+	Webhook Channel = "webhook"
 )
 
 type UpdateType string
diff --git a/util/k8s/k8sApplicationRestHandler.go b/util/k8s/k8sApplicationRestHandler.go
index 769359509..0d3d2c891 100644
--- a/util/k8s/k8sApplicationRestHandler.go
+++ b/util/k8s/k8sApplicationRestHandler.go
@@ -22,7 +22,6 @@ import (
 	"go.uber.org/zap"
 	errors3 "k8s.io/apimachinery/pkg/api/errors"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime/schema"
 	"net/http"
 	"strconv"
 	"strings"
@@ -127,9 +126,9 @@ func (handler *K8sApplicationRestHandlerImpl) GetResource(w http.ResponseWriter,
 	}
 	rbacObject := ""
 	rbacObject2 := ""
-
+	envObject := ""
 	token := r.Header.Get("token")
-	if request.AppId != "" {
+	if request.AppId != "" && request.AppType == HelmAppType {
 		appIdentifier, err := handler.helmAppService.DecodeAppId(request.AppId)
 		if err != nil {
 			handler.logger.Errorw("error in decoding appId", "err", err, "appId", request.AppId)
@@ -139,26 +138,54 @@ func (handler *K8sApplicationRestHandlerImpl) GetResource(w http.ResponseWriter,
 		//setting appIdentifier value in request
 		request.AppIdentifier = appIdentifier
 		request.ClusterId = request.AppIdentifier.ClusterId
-		valid, err := handler.k8sApplicationService.ValidateResourceRequest(r.Context(), request.AppIdentifier, request.K8sRequest)
-		if err != nil || !valid {
-			handler.logger.Errorw("error in validating resource request", "err", err)
+		if request.DeploymentType == HelmInstalledType {
+			valid, err := handler.k8sApplicationService.ValidateResourceRequest(r.Context(), request.AppIdentifier, request.K8sRequest)
+			if err != nil || !valid {
+				handler.logger.Errorw("error in validating resource request", "err", err)
+				common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+				return
+			}
+		} else if request.DeploymentType == ArgoInstalledType {
+			//TODO Implement ResourceRequest Validation for ArgoCD Installed APPs From ResourceTree
+		}
+		// RBAC enforcer applying for Helm App
+		rbacObject, rbacObject2 = handler.enforcerUtilHelm.GetHelmObjectByClusterIdNamespaceAndAppName(request.AppIdentifier.ClusterId, request.AppIdentifier.Namespace, request.AppIdentifier.ReleaseName)
+		ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject) || handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject2)
+		if !ok {
+			common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
+			return
+		}
+		// RBAC enforcer Ends
+	} else if request.AppId != "" && request.AppType == DevtronAppType {
+		devtronAppIdentifier, err := handler.k8sApplicationService.DecodeDevtronAppId(request.AppId)
+		if err != nil {
+			handler.logger.Errorw("error in decoding appId", "err", err, "appId", request.AppId)
 			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
 			return
 		}
-		// TODO: this rbac is commented because we are only checking helm apps access whereas this api is being used in devtron apps too
-		// this  needs to be updated with conditional rbac depending on where the call came from,until then this will get prevented with the view page permission
-		//rbacObject, rbacObject2 = handler.enforcerUtilHelm.GetHelmObjectByClusterIdNamespaceAndAppName(request.AppIdentifier.ClusterId, request.AppIdentifier.Namespace, request.AppIdentifier.ReleaseName)
-		//token := r.Header.Get("token")
-		//ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject) || handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject2)
-		//if !ok {
-		//	common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
-		//	return
-		//}
-	} else if request.ClusterId <= 0 {
+		//setting devtronAppIdentifier value in request
+		request.DevtronAppIdentifier = devtronAppIdentifier
+		request.ClusterId = request.DevtronAppIdentifier.ClusterId
+		if request.DeploymentType == HelmInstalledType {
+			//TODO Implement ResourceRequest Validation for Helm Installed Devtron APPs
+		} else if request.DeploymentType == ArgoInstalledType {
+			//TODO Implement ResourceRequest Validation for ArgoCD Installed APPs From ResourceTree
+		}
+		// RBAC enforcer applying for Devtron App
+		envObject = handler.enforcerUtil.GetEnvRBACNameByAppId(request.DevtronAppIdentifier.AppId, request.DevtronAppIdentifier.EnvId)
+		hasReadAccessForEnv := handler.enforcer.Enforce(token, casbin.ResourceEnvironment, casbin.ActionGet, envObject)
+		if !hasReadAccessForEnv {
+			common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
+			return
+		}
+		// RBAC enforcer Ends
+	}
+	// Invalid cluster id
+	if request.ClusterId <= 0 {
 		common.WriteJsonResp(w, errors.New("can not resource manifest as target cluster is not provided"), nil, http.StatusBadRequest)
 		return
 	}
-
+	// Fetching requested resource
 	resource, err := handler.k8sApplicationService.GetResource(r.Context(), &request)
 	if err != nil {
 		handler.logger.Errorw("error in getting resource", "err", err)
@@ -166,13 +193,19 @@ func (handler *K8sApplicationRestHandlerImpl) GetResource(w http.ResponseWriter,
 		return
 	}
 
-	canUpdate := true
-	if request.AppId != "" {
-		// Obfuscate secret if user does not have edit access
+	canUpdate := false
+	// Obfuscate secret if user does not have edit access
+	if request.AppIdentifier != nil {
+		// Verify update access for Helm Apps
 		canUpdate = handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionUpdate, rbacObject) || handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionUpdate, rbacObject2)
-	} else if request.ClusterId > 0 {
+	} else if request.DevtronAppIdentifier != nil {
+		// Verify update access for Devtron Apps
+		canUpdate = handler.enforcer.Enforce(token, casbin.ResourceEnvironment, casbin.ActionUpdate, envObject)
+	} else if request.AppIdentifier == nil && request.DevtronAppIdentifier == nil && request.ClusterId > 0 {
+		// Verify update access for Resource Browser
 		canUpdate = handler.k8sApplicationService.ValidateClusterResourceBean(r.Context(), request.ClusterId, resource.Manifest, request.K8sRequest.ResourceIdentifier.GroupVersionKind, handler.getRbacCallbackForResource(token, casbin.ActionUpdate))
 		if !canUpdate {
+			// Verify read access for Resource Browser
 			readAllowed := handler.k8sApplicationService.ValidateClusterResourceBean(r.Context(), request.ClusterId, resource.Manifest, request.K8sRequest.ResourceIdentifier.GroupVersionKind, handler.getRbacCallbackForResource(token, casbin.ActionGet))
 			if !readAllowed {
 				common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
@@ -181,6 +214,7 @@ func (handler *K8sApplicationRestHandlerImpl) GetResource(w http.ResponseWriter,
 		}
 	}
 	if !canUpdate && resource != nil {
+		// Hide secret for read only access
 		modifiedManifest, err := k8sObjectsUtil.HideValuesIfSecret(&resource.Manifest)
 		if err != nil {
 			handler.logger.Errorw("error in hiding secret values", "err", err)
@@ -297,9 +331,8 @@ func (handler *K8sApplicationRestHandlerImpl) UpdateResource(w http.ResponseWrit
 		common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
 		return
 	}
-
-	if len(request.AppId) > 0 {
-		// assume it as helm release case in which appId is supplied
+	if request.AppId != "" && request.AppType == HelmAppType {
+		// For helm app resources
 		appIdentifier, err := handler.helmAppService.DecodeAppId(request.AppId)
 		if err != nil {
 			handler.logger.Errorw("error in decoding appId", "err", err, "appId", request.AppId)
@@ -309,11 +342,15 @@ func (handler *K8sApplicationRestHandlerImpl) UpdateResource(w http.ResponseWrit
 		//setting appIdentifier value in request
 		request.AppIdentifier = appIdentifier
 		request.ClusterId = appIdentifier.ClusterId
-		valid, err := handler.k8sApplicationService.ValidateResourceRequest(r.Context(), request.AppIdentifier, request.K8sRequest)
-		if err != nil || !valid {
-			handler.logger.Errorw("error in validating resource request", "err", err)
-			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
-			return
+		if request.DeploymentType == HelmAppType {
+			valid, err := handler.k8sApplicationService.ValidateResourceRequest(r.Context(), request.AppIdentifier, request.K8sRequest)
+			if err != nil || !valid {
+				handler.logger.Errorw("error in validating resource request", "err", err)
+				common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+				return
+			}
+		} else if request.DeploymentType == ArgoInstalledType {
+			//TODO Implement ResourceRequest Validation for ArgoCD Installed APPs From ResourceTree
 		}
 		// RBAC enforcer applying
 		rbacObject, rbacObject2 := handler.enforcerUtilHelm.GetHelmObjectByClusterIdNamespaceAndAppName(request.AppIdentifier.ClusterId, request.AppIdentifier.Namespace, request.AppIdentifier.ReleaseName)
@@ -324,11 +361,36 @@ func (handler *K8sApplicationRestHandlerImpl) UpdateResource(w http.ResponseWrit
 			return
 		}
 		//RBAC enforcer Ends
+	} else if request.AppId != "" && request.AppType == DevtronAppType {
+		// For Devtron App resources
+		devtronAppIdentifier, err := handler.k8sApplicationService.DecodeDevtronAppId(request.AppId)
+		if err != nil {
+			handler.logger.Errorw("error in decoding appId", "err", err, "appId", request.AppId)
+			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+			return
+		}
+		//setting devtronAppIdentifier value in request
+		request.DevtronAppIdentifier = devtronAppIdentifier
+		request.ClusterId = request.DevtronAppIdentifier.ClusterId
+		if request.DeploymentType == HelmInstalledType {
+			//TODO Implement ResourceRequest Validation for Helm Installed Devtron APPs
+		} else if request.DeploymentType == ArgoInstalledType {
+			//TODO Implement ResourceRequest Validation for ArgoCD Installed APPs From ResourceTree
+		}
+		// RBAC enforcer applying for Devtron App
+		envObject := handler.enforcerUtil.GetEnvRBACNameByAppId(request.DevtronAppIdentifier.AppId, request.DevtronAppIdentifier.EnvId)
+		hasAccessForEnv := handler.enforcer.Enforce(token, casbin.ResourceEnvironment, casbin.ActionUpdate, envObject)
+		if !hasAccessForEnv {
+			common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
+			return
+		}
+		// RBAC enforcer Ends
 	} else if request.ClusterId > 0 {
-		// assume direct update in cluster
+		// RBAC enforcer applying for Resource Browser
 		if ok := handler.handleRbac(r, w, request, token, casbin.ActionUpdate); !ok {
 			return
 		}
+		// RBAC enforcer Ends
 	} else {
 		common.WriteJsonResp(w, errors.New("can not update resource as target cluster is not provided"), nil, http.StatusBadRequest)
 		return
@@ -344,6 +406,7 @@ func (handler *K8sApplicationRestHandlerImpl) UpdateResource(w http.ResponseWrit
 }
 
 func (handler *K8sApplicationRestHandlerImpl) handleRbac(r *http.Request, w http.ResponseWriter, request ResourceRequestBean, token string, casbinAction string) bool {
+	// assume direct update in cluster
 	allowed, err := handler.k8sApplicationService.ValidateClusterResourceRequest(r.Context(), &request, handler.getRbacCallbackForResource(token, casbinAction))
 	if err != nil {
 		common.WriteJsonResp(w, errors.New("invalid request"), nil, http.StatusBadRequest)
@@ -356,26 +419,22 @@ func (handler *K8sApplicationRestHandlerImpl) handleRbac(r *http.Request, w http
 }
 
 func (handler *K8sApplicationRestHandlerImpl) DeleteResource(w http.ResponseWriter, r *http.Request) {
-
 	userId, err := handler.userService.GetLoggedInUser(r)
-
 	if userId == 0 || err != nil {
 		common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
 		return
 	}
-
-	decoder := json.NewDecoder(r.Body)
-	token := r.Header.Get("token")
 	var request ResourceRequestBean
-	err = decoder.Decode(&request)
+	err = json.NewDecoder(r.Body).Decode(&request)
 	if err != nil {
 		handler.logger.Errorw("error in decoding request body", "err", err)
 		common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
 		return
 	}
+	token := r.Header.Get("token")
 
-	if len(request.AppId) > 0 {
-		// assume it as helm release case in which appId is supplied
+	if request.AppId != "" && request.AppType == HelmAppType {
+		// For Helm app resource
 		appIdentifier, err := handler.helmAppService.DecodeAppId(request.AppId)
 		if err != nil {
 			handler.logger.Errorw("error in decoding appId", "err", err, "appId", request.AppId)
@@ -385,15 +444,18 @@ func (handler *K8sApplicationRestHandlerImpl) DeleteResource(w http.ResponseWrit
 		//setting appIdentifier value in request
 		request.AppIdentifier = appIdentifier
 		request.ClusterId = appIdentifier.ClusterId
-		valid, err := handler.k8sApplicationService.ValidateResourceRequest(r.Context(), request.AppIdentifier, request.K8sRequest)
-		if err != nil || !valid {
-			handler.logger.Errorw("error in validating resource request", "err", err)
-			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
-			return
+		if request.DeploymentType == HelmInstalledType {
+			valid, err := handler.k8sApplicationService.ValidateResourceRequest(r.Context(), request.AppIdentifier, request.K8sRequest)
+			if err != nil || !valid {
+				handler.logger.Errorw("error in validating resource request", "err", err)
+				common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+				return
+			}
+		} else if request.DeploymentType == ArgoInstalledType {
+			//TODO Implement ResourceRequest Validation for ArgoCD Installed APPs From ResourceTree
 		}
-		// RBAC enforcer applying
+		// RBAC enforcer applying for Helm App
 		rbacObject, rbacObject2 := handler.enforcerUtilHelm.GetHelmObjectByClusterIdNamespaceAndAppName(request.AppIdentifier.ClusterId, request.AppIdentifier.Namespace, request.AppIdentifier.ReleaseName)
-		token := r.Header.Get("token")
 
 		ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionDelete, rbacObject) || handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionDelete, rbacObject2)
 
@@ -402,10 +464,36 @@ func (handler *K8sApplicationRestHandlerImpl) DeleteResource(w http.ResponseWrit
 			return
 		}
 		//RBAC enforcer Ends
+	} else if request.AppId != "" && request.AppType == DevtronAppType {
+		// For Devtron App resources
+		devtronAppIdentifier, err := handler.k8sApplicationService.DecodeDevtronAppId(request.AppId)
+		if err != nil {
+			handler.logger.Errorw("error in decoding appId", "err", err, "appId", request.AppId)
+			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+			return
+		}
+		//setting devtronAppIdentifier value in request
+		request.DevtronAppIdentifier = devtronAppIdentifier
+		request.ClusterId = request.DevtronAppIdentifier.ClusterId
+		if request.DeploymentType == HelmInstalledType {
+			//TODO Implement ResourceRequest Validation for Helm Installed Devtron APPs
+		} else if request.DeploymentType == ArgoInstalledType {
+			//TODO Implement ResourceRequest Validation for ArgoCD Installed APPs From ResourceTree
+		}
+		// RBAC enforcer applying for Devtron App
+		envObject := handler.enforcerUtil.GetEnvRBACNameByAppId(request.DevtronAppIdentifier.AppId, request.DevtronAppIdentifier.EnvId)
+		hasAccessForEnv := handler.enforcer.Enforce(token, casbin.ResourceEnvironment, casbin.ActionDelete, envObject)
+		if !hasAccessForEnv {
+			common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
+			return
+		}
+		// RBAC enforcer Ends
 	} else if request.ClusterId > 0 {
+		// RBAC enforcer applying for resource Browser
 		if ok := handler.handleRbac(r, w, request, token, casbin.ActionDelete); !ok {
 			return
 		}
+		// RBAC enforcer Ends
 	} else {
 		common.WriteJsonResp(w, errors.New("can not delete resource as target cluster is not provided"), nil, http.StatusBadRequest)
 		return
@@ -430,8 +518,8 @@ func (handler *K8sApplicationRestHandlerImpl) ListEvents(w http.ResponseWriter,
 		common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
 		return
 	}
-	if len(request.AppId) > 0 {
-		// assume it as helm release case in which appId is supplied
+	if request.AppId != "" && request.AppType == HelmAppType {
+		// For Helm app resource
 		appIdentifier, err := handler.helmAppService.DecodeAppId(request.AppId)
 		if err != nil {
 			handler.logger.Errorw("error in decoding appId", "err", err, "appId", request.AppId)
@@ -441,28 +529,54 @@ func (handler *K8sApplicationRestHandlerImpl) ListEvents(w http.ResponseWriter,
 		//setting appIdentifier value in request
 		request.AppIdentifier = appIdentifier
 		request.ClusterId = appIdentifier.ClusterId
-		valid, err := handler.k8sApplicationService.ValidateResourceRequest(r.Context(), request.AppIdentifier, request.K8sRequest)
-		if err != nil || !valid {
-			handler.logger.Errorw("error in validating resource request", "err", err)
+		if request.DeploymentType == HelmInstalledType {
+			valid, err := handler.k8sApplicationService.ValidateResourceRequest(r.Context(), request.AppIdentifier, request.K8sRequest)
+			if err != nil || !valid {
+				handler.logger.Errorw("error in validating resource request", "err", err)
+				common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+				return
+			}
+		} else if request.DeploymentType == ArgoInstalledType {
+			//TODO Implement ResourceRequest Validation for ArgoCD Installed APPs From ResourceTree
+		}
+		// RBAC enforcer applying for Helm App
+		rbacObject, rbacObject2 := handler.enforcerUtilHelm.GetHelmObjectByClusterIdNamespaceAndAppName(request.AppIdentifier.ClusterId, request.AppIdentifier.Namespace, request.AppIdentifier.ReleaseName)
+		ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject) || handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject2)
+		if !ok {
+			common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
+			return
+		}
+		//RBAC enforcer Ends
+	} else if request.AppId != "" && request.AppType == DevtronAppType {
+		// For Devtron App resources
+		devtronAppIdentifier, err := handler.k8sApplicationService.DecodeDevtronAppId(request.AppId)
+		if err != nil {
+			handler.logger.Errorw("error in decoding appId", "err", err, "appId", request.AppId)
 			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
 			return
 		}
-		// TODO: this rbac is commented because we are only checking helm apps access whereas this api is being used in devtron apps too
-		// this  needs to be updated with conditional rbac depending on where the call came from,until then this will get prevented with the view page permission
-		//rbacObject, rbacObject2 := handler.enforcerUtilHelm.GetHelmObjectByClusterIdNamespaceAndAppName(request.AppIdentifier.ClusterId, request.AppIdentifier.Namespace, request.AppIdentifier.ReleaseName)
-		//token := r.Header.Get("token")
-		//
-		//ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject) || handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject2)
-		//
-		//if !ok {
-		//	common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
-		//	return
-		//}
+		//setting devtronAppIdentifier value in request
+		request.DevtronAppIdentifier = devtronAppIdentifier
+		request.ClusterId = request.DevtronAppIdentifier.ClusterId
+		if request.DeploymentType == HelmInstalledType {
+			//TODO Implement ResourceRequest Validation for Helm Installed Devtron APPs
+		} else if request.DeploymentType == ArgoInstalledType {
+			//TODO Implement ResourceRequest Validation for ArgoCD Installed APPs From ResourceTree
+		}
+		//RBAC enforcer applying for Devtron App
+		envObject := handler.enforcerUtil.GetEnvRBACNameByAppId(request.DevtronAppIdentifier.AppId, request.DevtronAppIdentifier.EnvId)
+		hasAccessForEnv := handler.enforcer.Enforce(token, casbin.ResourceEnvironment, casbin.ActionGet, envObject)
+		if !hasAccessForEnv {
+			common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
+			return
+		}
 		//RBAC enforcer Ends
 	} else if request.ClusterId > 0 {
+		// RBAC enforcer applying for resource Browser
 		if ok := handler.handleRbac(r, w, request, token, casbin.ActionGet); !ok {
 			return
 		}
+		// RBAC enforcer Ends
 	} else {
 		common.WriteJsonResp(w, errors.New("can not get resource as target cluster is not provided"), nil, http.StatusBadRequest)
 		return
@@ -477,113 +591,63 @@ func (handler *K8sApplicationRestHandlerImpl) ListEvents(w http.ResponseWriter,
 }
 
 func (handler *K8sApplicationRestHandlerImpl) GetPodLogs(w http.ResponseWriter, r *http.Request) {
-	v := r.URL.Query()
-	vars := mux.Vars(r)
-	podName := vars["podName"]
-	containerName := v.Get("containerName")
-	appId := v.Get("appId")
-	clusterIdString := v.Get("clusterId")
-	namespace := v.Get("namespace")
-	/*sinceSeconds, err := strconv.Atoi(v.Get("sinceSeconds"))
-	if err != nil {
-		sinceSeconds = 0
-	}*/
 	token := r.Header.Get("token")
-	follow, err := strconv.ParseBool(v.Get("follow"))
-	if err != nil {
-		follow = false
-	}
-	tailLines, err := strconv.Atoi(v.Get("tailLines"))
+	request, err := handler.k8sApplicationService.ValidatePodLogsRequestQuery(r)
 	if err != nil {
-		tailLines = 0
+		common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+		return
 	}
-	var request *ResourceRequestBean
-	if appId != "" {
-		appIdentifier, err := handler.helmAppService.DecodeAppId(appId)
-		if err != nil {
-			handler.logger.Errorw("error in decoding appId", "err", err, "appId", appId)
-			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
-			return
-		}
-		request = &ResourceRequestBean{
-			AppIdentifier: appIdentifier,
-			ClusterId:     appIdentifier.ClusterId,
-			K8sRequest: &application.K8sRequestBean{
-				ResourceIdentifier: application.ResourceIdentifier{
-					Name:             podName,
-					Namespace:        appIdentifier.Namespace,
-					GroupVersionKind: schema.GroupVersionKind{},
-				},
-				PodLogsRequest: application.PodLogsRequest{
-					//SinceTime:     sinceSeconds,
-					TailLines:     tailLines,
-					Follow:        follow,
-					ContainerName: containerName,
-				},
-			},
-		}
-
-		valid, err := handler.k8sApplicationService.ValidateResourceRequest(r.Context(), request.AppIdentifier, request.K8sRequest)
-		if err != nil || !valid {
-			handler.logger.Errorw("error in validating resource request", "err", err)
-			apiError := util2.ApiError{
-				InternalMessage: "failed to validate the resource with error " + err.Error(),
-				UserMessage:     "Failed to validate resource",
-			}
-			if !valid {
-				apiError.InternalMessage = "failed to validate the resource"
-				apiError.UserMessage = "requested Pod or Container doesn't exist"
+	if request.AppIdentifier != nil {
+		if request.DeploymentType == HelmInstalledType {
+			valid, err := handler.k8sApplicationService.ValidateResourceRequest(r.Context(), request.AppIdentifier, request.K8sRequest)
+			if err != nil || !valid {
+				handler.logger.Errorw("error in validating resource request", "err", err)
+				apiError := util2.ApiError{
+					InternalMessage: "failed to validate the resource with error " + err.Error(),
+					UserMessage:     "Failed to validate resource",
+				}
+				if !valid {
+					apiError.InternalMessage = "failed to validate the resource"
+					apiError.UserMessage = "requested Pod or Container doesn't exist"
+				}
+				common.WriteJsonResp(w, &apiError, nil, http.StatusBadRequest)
+				return
 			}
-			common.WriteJsonResp(w, &apiError, nil, http.StatusBadRequest)
+		} else if request.DeploymentType == ArgoInstalledType {
+			//TODO Implement ResourceRequest Validation for ArgoCD Installed APPs From ResourceTree
+		}
+		// RBAC enforcer applying for Helm App
+		rbacObject, rbacObject2 := handler.enforcerUtilHelm.GetHelmObjectByClusterIdNamespaceAndAppName(request.AppIdentifier.ClusterId, request.AppIdentifier.Namespace, request.AppIdentifier.ReleaseName)
+		ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject) || handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject2)
+
+		if !ok {
+			common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
 			return
 		}
-		// TODO: this rbac is commented because we are only checking helm apps access whereas this api is being used in devtron apps too
-		// this  needs to be updated with conditional rbac depending on where the call came from,until then this will get prevented with the view page permission
-		//rbacObject, rbacObject2 := handler.enforcerUtilHelm.GetHelmObjectByClusterIdNamespaceAndAppName(request.AppIdentifier.ClusterId, request.AppIdentifier.Namespace, request.AppIdentifier.ReleaseName)
-		//token := r.Header.Get("token")
-		//
-		//ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject) || handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject2)
-		//
-		//if !ok {
-		//	common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
-		//	return
-		//}
 		//RBAC enforcer Ends
-	} else if clusterIdString != "" && namespace != "" {
-		clusterId, err := strconv.Atoi(clusterIdString)
-		if err != nil {
-			handler.logger.Errorw("invalid cluster id", "clusterId", clusterIdString, "err", err)
-			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+	} else if request.DevtronAppIdentifier != nil {
+		if request.DeploymentType == HelmInstalledType {
+			//TODO Implement ResourceRequest Validation for Helm Installed Devtron APPs
+		} else if request.DeploymentType == ArgoInstalledType {
+			//TODO Implement ResourceRequest Validation for ArgoCD Installed APPs From ResourceTree
+		}
+		// RBAC enforcer applying For Devtron App
+		envObject := handler.enforcerUtil.GetEnvRBACNameByAppId(request.DevtronAppIdentifier.AppId, request.DevtronAppIdentifier.EnvId)
+		if !handler.enforcer.Enforce(token, casbin.ResourceEnvironment, casbin.ActionGet, envObject) {
+			common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
 			return
 		}
-		request = &ResourceRequestBean{
-			ClusterId: clusterId,
-			K8sRequest: &application.K8sRequestBean{
-				ResourceIdentifier: application.ResourceIdentifier{
-					Name:      podName,
-					Namespace: namespace,
-					GroupVersionKind: schema.GroupVersionKind{
-						Group:   "",
-						Kind:    "Pod",
-						Version: "v1",
-					},
-				},
-				PodLogsRequest: application.PodLogsRequest{
-					//SinceTime:     sinceSeconds,
-					TailLines:     tailLines,
-					Follow:        follow,
-					ContainerName: containerName,
-				},
-			},
-		}
-		if ok := handler.handleRbac(r, w, *request, token, casbin.ActionGet); !ok {
+		//RBAC enforcer Ends
+	} else if request.AppIdentifier == nil && request.DevtronAppIdentifier == nil && request.ClusterId > 0 {
+		//RBAC enforcer applying For Resource Browser
+		if !handler.handleRbac(r, w, *request, token, casbin.ActionGet) {
 			return
 		}
+		//RBAC enforcer Ends
 	} else {
-		common.WriteJsonResp(w, errors.New("can not get pod logs as target cluster or namespace is not provided"), nil, http.StatusBadRequest)
+		common.WriteJsonResp(w, errors.New("can not get pod logs as target cluster is not provided"), nil, http.StatusBadRequest)
 		return
 	}
-
 	lastEventId := r.Header.Get("Last-Event-ID")
 	isReconnect := false
 	if len(lastEventId) > 0 {
@@ -615,68 +679,36 @@ func (handler *K8sApplicationRestHandlerImpl) GetPodLogs(w http.ResponseWriter,
 }
 
 func (handler *K8sApplicationRestHandlerImpl) GetTerminalSession(w http.ResponseWriter, r *http.Request) {
-	request := &terminal.TerminalSessionRequest{}
-	vars := mux.Vars(r)
 	token := r.Header.Get("token")
-	request.ContainerName = vars["container"]
-	request.Namespace = vars["namespace"]
-	request.PodName = vars["pod"]
-	request.Shell = vars["shell"]
-	clusterIdString := ""
-	appId := ""
-	identifier := vars["identifier"]
-	if strings.Contains(identifier, "|") {
-		appId = identifier
-	} else {
-		clusterIdString = identifier
+	request, resourceRequestBean, err := handler.k8sApplicationService.ValidateTerminalRequestQuery(r)
+	if err != nil {
+		common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+		return
 	}
-
-	if appId != "" {
-		request.ApplicationId = appId
-		app, err := handler.helmAppService.DecodeAppId(request.ApplicationId)
-		if err != nil {
-			handler.logger.Errorw("invalid app id", "err", err)
-			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
-			return
-		}
-		request.ClusterId = app.ClusterId
-
-		// RBAC enforcer applying
-		rbacObject, rbacObject2 := handler.enforcerUtilHelm.GetHelmObjectByClusterIdNamespaceAndAppName(app.ClusterId, app.Namespace, app.ReleaseName)
-		token := r.Header.Get("token")
-
-		ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject) || handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, rbacObject2)
+	if resourceRequestBean.AppIdentifier != nil {
+		// RBAC enforcer applying For Helm App
+		rbacObject, rbacObject2 := handler.enforcerUtilHelm.GetHelmObjectByClusterIdNamespaceAndAppName(resourceRequestBean.AppIdentifier.ClusterId, resourceRequestBean.AppIdentifier.Namespace, resourceRequestBean.AppIdentifier.ReleaseName)
+		ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionUpdate, rbacObject) || handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionUpdate, rbacObject2)
 
 		if !ok {
 			common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
 			return
 		}
 		//RBAC enforcer Ends
-	} else if clusterIdString != "" {
-		clusterId, err := strconv.Atoi(clusterIdString)
-		if err != nil {
-			handler.logger.Errorw("invalid cluster id", "clusterId", clusterIdString, "err", err)
-			common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
+	} else if resourceRequestBean.DevtronAppIdentifier != nil {
+		// RBAC enforcer applying For Devtron App
+		envObject := handler.enforcerUtil.GetEnvRBACNameByAppId(resourceRequestBean.DevtronAppIdentifier.AppId, resourceRequestBean.DevtronAppIdentifier.EnvId)
+		if !handler.enforcer.Enforce(token, casbin.ResourceEnvironment, casbin.ActionUpdate, envObject) {
+			common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden)
 			return
 		}
-		request.ClusterId = clusterId
-		resourceRequestBean := ResourceRequestBean{
-			ClusterId: clusterId,
-			K8sRequest: &application.K8sRequestBean{
-				ResourceIdentifier: application.ResourceIdentifier{
-					Name:      request.PodName,
-					Namespace: request.Namespace,
-					GroupVersionKind: schema.GroupVersionKind{
-						Group:   "",
-						Kind:    "Pod",
-						Version: "v1",
-					},
-				},
-			},
-		}
-		if ok := handler.handleRbac(r, w, resourceRequestBean, token, casbin.ActionUpdate); !ok {
+		//RBAC enforcer Ends
+	} else if resourceRequestBean.AppIdentifier == nil && resourceRequestBean.DevtronAppIdentifier == nil && resourceRequestBean.ClusterId > 0 {
+		//RBAC enforcer applying for Resource Browser
+		if !handler.handleRbac(r, w, *resourceRequestBean, token, casbin.ActionUpdate) {
 			return
 		}
+		//RBAC enforcer Ends
 	} else {
 		common.WriteJsonResp(w, errors.New("can not get terminal session as target cluster is not provided"), nil, http.StatusBadRequest)
 		return
diff --git a/util/k8s/k8sApplicationService.go b/util/k8s/k8sApplicationService.go
index d2589333c..c44c71a69 100644
--- a/util/k8s/k8sApplicationService.go
+++ b/util/k8s/k8sApplicationService.go
@@ -4,6 +4,13 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"io"
+	"net/http"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
 	"github.com/argoproj/gitops-engine/pkg/utils/kube"
 	"github.com/caarlos0/env"
 	"github.com/devtron-labs/devtron/api/bean"
@@ -14,12 +21,13 @@ import (
 	"github.com/devtron-labs/devtron/internal/util"
 	"github.com/devtron-labs/devtron/pkg/cluster"
 	"github.com/devtron-labs/devtron/pkg/kubernetesResourceAuditLogs"
+	"github.com/devtron-labs/devtron/pkg/terminal"
 	"github.com/devtron-labs/devtron/pkg/user/casbin"
 	util3 "github.com/devtron-labs/devtron/pkg/util"
 	yamlUtil "github.com/devtron-labs/devtron/util/yaml"
+	"github.com/gorilla/mux"
 	"go.opentelemetry.io/otel"
 	"go.uber.org/zap"
-	"io"
 	errors2 "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -29,10 +37,6 @@ import (
 	"k8s.io/client-go/rest"
 	"log"
 	"net/url"
-	"strconv"
-	"strings"
-	"sync"
-	"time"
 )
 
 const (
@@ -40,6 +44,9 @@ const (
 )
 
 type K8sApplicationService interface {
+	ValidatePodLogsRequestQuery(r *http.Request) (*ResourceRequestBean, error)
+	ValidateTerminalRequestQuery(r *http.Request) (*terminal.TerminalSessionRequest, *ResourceRequestBean, error)
+	DecodeDevtronAppId(applicationId string) (*DevtronAppIdentifier, error)
 	GetResource(ctx context.Context, request *ResourceRequestBean) (resp *application.ManifestResponse, err error)
 	CreateResource(ctx context.Context, request *ResourceRequestBean) (resp *application.ManifestResponse, err error)
 	UpdateResource(ctx context.Context, request *ResourceRequestBean) (resp *application.ManifestResponse, err error)
@@ -101,22 +108,234 @@ func NewK8sApplicationServiceImpl(Logger *zap.SugaredLogger,
 	}
 }
 
+const (
+	// App Type Identifiers
+	DevtronAppType = 0 // Identifier for Devtron Apps
+	HelmAppType    = 1 // Identifier for Helm Apps
+
+	// Deployment Type Identifiers
+	HelmInstalledType = 0 // Identifier for Helm deployment
+	ArgoInstalledType = 1 // Identifier for ArgoCD deployment
+)
+
 type ResourceRequestBean struct {
-	AppId         string                      `json:"appId"`
-	AppIdentifier *client.AppIdentifier       `json:"-"`
-	K8sRequest    *application.K8sRequestBean `json:"k8sRequest"`
-	ClusterId     int                         `json:"clusterId"` // clusterId is used when request is for direct cluster (not for helm release)
+	AppId                string                      `json:"appId"`
+	AppType              int                         `json:"appType,omitempty"`        // 0: DevtronApp, 1: HelmApp
+	DeploymentType       int                         `json:"deploymentType,omitempty"` // 0: DevtronApp, 1: HelmApp
+	AppIdentifier        *client.AppIdentifier       `json:"-"`
+	K8sRequest           *application.K8sRequestBean `json:"k8sRequest"`
+	DevtronAppIdentifier *DevtronAppIdentifier       `json:"-"`         // For Devtron App Resources
+	ClusterId            int                         `json:"clusterId"` // clusterId is used when request is for direct cluster (not for helm release)
 }
 
 type ResourceInfo struct {
 	PodName string `json:"podName"`
 }
 
+type DevtronAppIdentifier struct {
+	ClusterId int `json:"clusterId"`
+	AppId     int `json:"appId"`
+	EnvId     int `json:"envId"`
+}
+
 type BatchResourceResponse struct {
 	ManifestResponse *application.ManifestResponse
 	Err              error
 }
 
+func (impl *K8sApplicationServiceImpl) ValidatePodLogsRequestQuery(r *http.Request) (*ResourceRequestBean, error) {
+	v, vars := r.URL.Query(), mux.Vars(r)
+	request := &ResourceRequestBean{}
+	podName := vars["podName"]
+	/*sinceSeconds, err := strconv.Atoi(v.Get("sinceSeconds"))
+	if err != nil {
+		sinceSeconds = 0
+	}*/
+	containerName, clusterIdString := v.Get("containerName"), v.Get("clusterId")
+	prevContainerLogs := v.Get("previous")
+	isPrevLogs, err := strconv.ParseBool(prevContainerLogs)
+	if err != nil {
+		isPrevLogs = false
+	}
+	appId := v.Get("appId")
+	follow, err := strconv.ParseBool(v.Get("follow"))
+	if err != nil {
+		follow = false
+	}
+	tailLines, err := strconv.Atoi(v.Get("tailLines"))
+	if err != nil {
+		tailLines = 0
+	}
+	k8sRequest := &application.K8sRequestBean{
+		ResourceIdentifier: application.ResourceIdentifier{
+			Name:             podName,
+			GroupVersionKind: schema.GroupVersionKind{},
+		},
+		PodLogsRequest: application.PodLogsRequest{
+			//SinceTime:     sinceSeconds,
+			TailLines:                  tailLines,
+			Follow:                     follow,
+			ContainerName:              containerName,
+			IsPrevContainerLogsEnabled: isPrevLogs,
+		},
+	}
+	request.K8sRequest = k8sRequest
+	if appId != "" {
+		// Validate App Type
+		appType, err := strconv.Atoi(v.Get("appType"))
+		if err != nil || !(appType == DevtronAppType || appType == HelmAppType) {
+			impl.logger.Errorw("Invalid appType", "err", err, "appType", appType)
+			return nil, err
+		}
+		request.AppType = appType
+		// Validate Deployment Type
+		deploymentType, err := strconv.Atoi(v.Get("deploymentType"))
+		if err != nil || !(deploymentType == HelmInstalledType || deploymentType == ArgoInstalledType) {
+			impl.logger.Errorw("Invalid deploymentType", "err", err, "deploymentType", deploymentType)
+			return nil, err
+		}
+		request.DeploymentType = deploymentType
+		// Validate App Id
+		if request.AppType == HelmAppType {
+			// For Helm App resources
+			appIdentifier, err := impl.helmAppService.DecodeAppId(appId)
+			if err != nil {
+				impl.logger.Errorw("error in decoding appId", "err", err, "appId", appId)
+				return nil, err
+			}
+			request.AppIdentifier = appIdentifier
+			request.ClusterId = appIdentifier.ClusterId
+			request.K8sRequest.ResourceIdentifier.Namespace = appIdentifier.Namespace
+		} else if request.AppType == DevtronAppType {
+			// For Devtron App resources
+			devtronAppIdentifier, err := impl.DecodeDevtronAppId(appId)
+			if err != nil {
+				impl.logger.Errorw("error in decoding appId", "err", err, "appId", request.AppId)
+				return nil, err
+			}
+			request.DevtronAppIdentifier = devtronAppIdentifier
+			request.ClusterId = devtronAppIdentifier.ClusterId
+			namespace := v.Get("namespace")
+			if namespace == "" {
+				err = fmt.Errorf("missing required field namespace")
+				impl.logger.Errorw("empty namespace", "err", err, "appId", request.AppId)
+				return nil, err
+			}
+			request.K8sRequest.ResourceIdentifier.Namespace = namespace
+		}
+	} else if clusterIdString != "" {
+		// Validate Cluster Id
+		clusterId, err := strconv.Atoi(clusterIdString)
+		if err != nil {
+			impl.logger.Errorw("invalid cluster id", "clusterId", clusterIdString, "err", err)
+			return nil, err
+		}
+		request.ClusterId = clusterId
+		namespace := v.Get("namespace")
+		if namespace == "" {
+			err = fmt.Errorf("missing required field namespace")
+			impl.logger.Errorw("empty namespace", "err", err, "appId", request.AppId)
+			return nil, err
+		}
+		request.K8sRequest.ResourceIdentifier.Namespace = namespace
+		request.K8sRequest.ResourceIdentifier.GroupVersionKind = schema.GroupVersionKind{
+			Group:   "",
+			Kind:    "Pod",
+			Version: "v1",
+		}
+	}
+	return request, nil
+}
+
+func (impl *K8sApplicationServiceImpl) ValidateTerminalRequestQuery(r *http.Request) (*terminal.TerminalSessionRequest, *ResourceRequestBean, error) {
+	request := &terminal.TerminalSessionRequest{}
+	v := r.URL.Query()
+	vars := mux.Vars(r)
+	request.ContainerName = vars["container"]
+	request.Namespace = vars["namespace"]
+	request.PodName = vars["pod"]
+	request.Shell = vars["shell"]
+	resourceRequestBean := &ResourceRequestBean{}
+	identifier := vars["identifier"]
+	if strings.Contains(identifier, "|") {
+		// Validate App Type
+		appType, err := strconv.Atoi(v.Get("appType"))
+		if err != nil || appType < DevtronAppType && appType > HelmAppType {
+			impl.logger.Errorw("Invalid appType", "err", err, "appType", appType)
+			return nil, nil, err
+		}
+		request.ApplicationId = identifier
+		if appType == HelmAppType {
+			appIdentifier, err := impl.helmAppService.DecodeAppId(request.ApplicationId)
+			if err != nil {
+				impl.logger.Errorw("invalid app id", "err", err, "appId", request.ApplicationId)
+				return nil, nil, err
+			}
+			resourceRequestBean.AppIdentifier = appIdentifier
+			resourceRequestBean.ClusterId = appIdentifier.ClusterId
+			request.ClusterId = appIdentifier.ClusterId
+		} else if appType == DevtronAppType {
+			devtronAppIdentifier, err := impl.DecodeDevtronAppId(request.ApplicationId)
+			if err != nil {
+				impl.logger.Errorw("invalid app id", "err", err, "appId", request.ApplicationId)
+				return nil, nil, err
+			}
+			resourceRequestBean.DevtronAppIdentifier = devtronAppIdentifier
+			resourceRequestBean.ClusterId = devtronAppIdentifier.ClusterId
+			request.ClusterId = devtronAppIdentifier.ClusterId
+		}
+	} else {
+		// Validate Cluster Id
+		clsuterId, err := strconv.Atoi(identifier)
+		if err != nil || clsuterId <= 0 {
+			impl.logger.Errorw("Invalid cluster id", "err", err, "clusterId", identifier)
+			return nil, nil, err
+		}
+		resourceRequestBean.ClusterId = clsuterId
+		request.ClusterId = clsuterId
+		k8sRequest := &application.K8sRequestBean{
+			ResourceIdentifier: application.ResourceIdentifier{
+				Name:      request.PodName,
+				Namespace: request.Namespace,
+				GroupVersionKind: schema.GroupVersionKind{
+					Group:   "",
+					Kind:    "Pod",
+					Version: "v1",
+				},
+			},
+		}
+		resourceRequestBean.K8sRequest = k8sRequest
+	}
+	return request, resourceRequestBean, nil
+}
+
+func (impl *K8sApplicationServiceImpl) DecodeDevtronAppId(applicationId string) (*DevtronAppIdentifier, error) {
+	component := strings.Split(applicationId, "|")
+	if len(component) != 3 {
+		return nil, fmt.Errorf("malformed app id %s", applicationId)
+	}
+	clusterId, err := strconv.Atoi(component[0])
+	if err != nil {
+		return nil, err
+	}
+	appId, err := strconv.Atoi(component[1])
+	if err != nil {
+		return nil, err
+	}
+	envId, err := strconv.Atoi(component[2])
+	if err != nil {
+		return nil, err
+	}
+	if clusterId <= 0 || appId <= 0 || envId <= 0 {
+		return nil, fmt.Errorf("invalid app identifier")
+	}
+	return &DevtronAppIdentifier{
+		ClusterId: clusterId,
+		AppId:     appId,
+		EnvId:     envId,
+	}, nil
+}
+
 func (impl *K8sApplicationServiceImpl) FilterServiceAndIngress(ctx context.Context, resourceTree map[string]interface{}, validRequests []ResourceRequestBean, appDetail bean.AppDetailContainer, appId string) []ResourceRequestBean {
 	noOfNodes := len(resourceTree["nodes"].([]interface{}))
 	resourceNodeItemss := resourceTree["nodes"].([]interface{})
diff --git a/wire_gen.go b/wire_gen.go
index c4dce8d23..03617afbb 100644
--- a/wire_gen.go
+++ b/wire_gen.go
@@ -537,13 +537,15 @@ func InitializeApp() (*App, error) {
 	notificationSettingsRepositoryImpl := repository.NewNotificationSettingsRepositoryImpl(db)
 	notificationConfigBuilderImpl := notifier.NewNotificationConfigBuilderImpl(sugaredLogger)
 	slackNotificationRepositoryImpl := repository.NewSlackNotificationRepositoryImpl(db)
+	webhookNotificationRepositoryImpl := repository.NewWebhookNotificationRepositoryImpl(db)
 	sesNotificationRepositoryImpl := repository.NewSESNotificationRepositoryImpl(db)
 	smtpNotificationRepositoryImpl := repository.NewSMTPNotificationRepositoryImpl(db)
-	notificationConfigServiceImpl := notifier.NewNotificationConfigServiceImpl(sugaredLogger, notificationSettingsRepositoryImpl, notificationConfigBuilderImpl, ciPipelineRepositoryImpl, pipelineRepositoryImpl, slackNotificationRepositoryImpl, sesNotificationRepositoryImpl, smtpNotificationRepositoryImpl, teamRepositoryImpl, environmentRepositoryImpl, appRepositoryImpl, userRepositoryImpl, ciPipelineMaterialRepositoryImpl)
-	slackNotificationServiceImpl := notifier.NewSlackNotificationServiceImpl(sugaredLogger, slackNotificationRepositoryImpl, teamServiceImpl, userRepositoryImpl, notificationSettingsRepositoryImpl)
+	notificationConfigServiceImpl := notifier.NewNotificationConfigServiceImpl(sugaredLogger, notificationSettingsRepositoryImpl, notificationConfigBuilderImpl, ciPipelineRepositoryImpl, pipelineRepositoryImpl, slackNotificationRepositoryImpl, webhookNotificationRepositoryImpl, sesNotificationRepositoryImpl, smtpNotificationRepositoryImpl, teamRepositoryImpl, environmentRepositoryImpl, appRepositoryImpl, userRepositoryImpl, ciPipelineMaterialRepositoryImpl)
+	slackNotificationServiceImpl := notifier.NewSlackNotificationServiceImpl(sugaredLogger, slackNotificationRepositoryImpl, webhookNotificationRepositoryImpl, teamServiceImpl, userRepositoryImpl, notificationSettingsRepositoryImpl)
+	webhookNotificationServiceImpl := notifier.NewWebhookNotificationServiceImpl(sugaredLogger, webhookNotificationRepositoryImpl, teamServiceImpl, userRepositoryImpl, notificationSettingsRepositoryImpl)
 	sesNotificationServiceImpl := notifier.NewSESNotificationServiceImpl(sugaredLogger, sesNotificationRepositoryImpl, teamServiceImpl, notificationSettingsRepositoryImpl)
 	smtpNotificationServiceImpl := notifier.NewSMTPNotificationServiceImpl(sugaredLogger, smtpNotificationRepositoryImpl, teamServiceImpl, notificationSettingsRepositoryImpl)
-	notificationRestHandlerImpl := restHandler.NewNotificationRestHandlerImpl(dockerRegistryConfigImpl, sugaredLogger, gitRegistryConfigImpl, dbConfigServiceImpl, userServiceImpl, validate, notificationConfigServiceImpl, slackNotificationServiceImpl, sesNotificationServiceImpl, smtpNotificationServiceImpl, enforcerImpl, teamServiceImpl, environmentServiceImpl, pipelineBuilderImpl, enforcerUtilImpl)
+	notificationRestHandlerImpl := restHandler.NewNotificationRestHandlerImpl(dockerRegistryConfigImpl, sugaredLogger, gitRegistryConfigImpl, dbConfigServiceImpl, userServiceImpl, validate, notificationConfigServiceImpl, slackNotificationServiceImpl, webhookNotificationServiceImpl, sesNotificationServiceImpl, smtpNotificationServiceImpl, enforcerImpl, teamServiceImpl, environmentServiceImpl, pipelineBuilderImpl, enforcerUtilImpl)
 	notificationRouterImpl := router.NewNotificationRouterImpl(notificationRestHandlerImpl)
 	teamRestHandlerImpl := team2.NewTeamRestHandlerImpl(sugaredLogger, teamServiceImpl, userServiceImpl, enforcerImpl, validate, userAuthServiceImpl, deleteServiceExtendedImpl)
 	teamRouterImpl := team2.NewTeamRouterImpl(teamRestHandlerImpl)


Test patch:
diff --git a/pkg/notifier/WebhookNotificationService_test.go b/pkg/notifier/WebhookNotificationService_test.go
new file mode 100644
index 000000000..4504b67ad
--- /dev/null
+++ b/pkg/notifier/WebhookNotificationService_test.go
@@ -0,0 +1,143 @@
+package notifier
+
+import (
+	"fmt"
+	"github.com/devtron-labs/devtron/internal/sql/repository"
+	mocks2 "github.com/devtron-labs/devtron/internal/sql/repository/mocks"
+	util2 "github.com/devtron-labs/devtron/internal/util"
+	"github.com/devtron-labs/devtron/pkg/team/mocks"
+	"github.com/stretchr/testify/mock"
+
+	//"github.com/devtron-labs/devtron/pkg/user/repository"
+	mocks3 "github.com/devtron-labs/devtron/pkg/user/repository/mocks"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func Test_buildWebhookNewConfigs(t *testing.T) {
+	type args struct {
+		webhookReq []WebhookConfigDto
+		userId     int32
+	}
+	tests := []struct {
+		name string
+		args args
+		want []*repository.WebhookConfig
+	}{
+		{
+			name: "test1",
+			args: args{
+				webhookReq: []WebhookConfigDto{
+					{
+						WebhookUrl: "dfcd nmc dc",
+						ConfigName: "aditya",
+						Payload:    map[string]interface{}{"text": "final"},
+						Header:     map[string]interface{}{"Content-type": "application/json"},
+					},
+				},
+				userId: 1,
+			},
+			want: []*repository.WebhookConfig{
+				{
+					WebHookUrl: "dfcd nmc dc",
+					ConfigName: "aditya",
+					Payload:    map[string]interface{}{"text": "final"},
+					Header:     map[string]interface{}{"Content-type": "application/json"},
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := buildWebhookNewConfigs(tt.args.webhookReq, tt.args.userId)
+
+			assert.Equal(t, len(tt.want), len(got), "Number of webhook configs mismatch")
+
+			for i, want := range tt.want {
+				assert.Equal(t, want.WebHookUrl, got[i].WebHookUrl, "WebHookUrl mismatch")
+				assert.Equal(t, want.ConfigName, got[i].ConfigName, "ConfigName mismatch")
+				assert.Equal(t, want.Payload, got[i].Payload, "Payload mismatch")
+				assert.Equal(t, want.Header, got[i].Header, "Header mismatch")
+
+			}
+		})
+	}
+}
+
+func TestWebhookNotificationServiceImpl_SaveOrEditNotificationConfig(t *testing.T) {
+	sugaredLogger, err := util2.NewSugardLogger()
+	assert.Nil(t, err)
+	mockedTeamService := mocks.NewTeamService(t)
+	mockedWebhookNotfRep := mocks2.NewWebhookNotificationRepository(t)
+	mockedUserRepo := mocks3.NewUserRepository(t)
+	mockedNotfSetRepo := mocks2.NewNotificationSettingsRepository(t)
+
+	type args struct {
+		channelReq []WebhookConfigDto
+		userId     int32
+	}
+
+	tests := []struct {
+		name    string
+		args    args
+		want    []int
+		wantErr assert.ErrorAssertionFunc
+	}{
+		{
+			name: "SaveOrUpdate_ExistingConfig",
+			args: args{
+				channelReq: []WebhookConfigDto{
+					{
+						WebhookUrl: "djfndgfbd,gds",
+						ConfigName: "aditya",
+						Payload:    map[string]interface{}{"text": "final"},
+						Header:     map[string]interface{}{"Content-type": "application/json"},
+					},
+				},
+				userId: 2,
+			},
+			want:    []int{0},
+			wantErr: assert.NoError,
+		},
+		{
+			name: "SaveOrUpdate_NewConfig",
+			args: args{
+				channelReq: []WebhookConfigDto{
+					{
+						WebhookUrl: "d,fm sdfd",
+						ConfigName: "aditya",
+						Payload:    map[string]interface{}{"text": "final"},
+						Header:     map[string]interface{}{"Content-type": "application/json"},
+					},
+				},
+				userId: 2,
+			},
+			want:    []int{0},
+			wantErr: assert.NoError,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			impl := &WebhookNotificationServiceImpl{
+				logger:                         sugaredLogger,
+				webhookRepository:              mockedWebhookNotfRep,
+				teamService:                    mockedTeamService,
+				userRepository:                 mockedUserRepo,
+				notificationSettingsRepository: mockedNotfSetRepo,
+			}
+
+			mockConfig := &repository.WebhookConfig{Id: 1}
+			mockError := error(nil)
+			mockedWebhookNotfRep.On("SaveWebhookConfig", mock.Anything).Return(mockConfig, mockError)
+
+			got, err := impl.SaveOrEditNotificationConfig(tt.args.channelReq, tt.args.userId)
+
+			if !tt.wantErr(t, err, fmt.Sprintf("SaveOrEditNotificationConfig(%v, %v)", tt.args.channelReq, tt.args.userId)) {
+				return
+			}
+			assert.Equalf(t, tt.want, got, "SaveOrEditNotificationConfig(%v, %v)", tt.args.channelReq, tt.args.userId)
+		})
+	}
+}