feat(webhook): add support for webhook PRs (#8435)

* feat: pr webhook

* feat: pr webhook

* feat: finish PR Webhook and Frontend Updates

* fix: cleanup and comments

* fix: go deps

* fix: base_repo_id static for webhook

fix: ui
diff --git a/backend/go.mod b/backend/go.mod
index 6a43821..7dd96bc 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -9,7 +9,7 @@
 	github.com/go-errors/errors v1.4.2 // indirect
 	github.com/go-git/go-git/v5 v5.12.0
 	github.com/go-playground/validator/v10 v10.19.0
-	github.com/gocarina/gocsv v0.0.0-20220707092902-b9da1f06c77e
+	github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
 	github.com/google/uuid v1.3.0
 	github.com/jackc/pgx/v5 v5.6.0 // indirect
 	github.com/lib/pq v1.10.2
@@ -127,4 +127,6 @@
 	golang.org/x/mod v0.17.0
 )
 
+replace github.com/chenzhuoyu/iasm => github.com/cloudwego/iasm v0.2.0
+
 //replace github.com/apache/incubator-devlake => ./
diff --git a/backend/go.sum b/backend/go.sum
index dc22ad7..0e44f0c 100644
--- a/backend/go.sum
+++ b/backend/go.sum
@@ -80,6 +80,7 @@
 github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
 github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
 github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -178,6 +179,8 @@
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gocarina/gocsv v0.0.0-20220707092902-b9da1f06c77e h1:GMIV+S6grz+vlIaUsP+fedQ6L+FovyMPMY26WO8dwQE=
 github.com/gocarina/gocsv v0.0.0-20220707092902-b9da1f06c77e/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
+github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
+github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
 github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
 github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
diff --git a/backend/plugins/webhook/api/blueprint_v200.go b/backend/plugins/webhook/api/blueprint_v200.go
index eaa6be3..6dca8ff 100644
--- a/backend/plugins/webhook/api/blueprint_v200.go
+++ b/backend/plugins/webhook/api/blueprint_v200.go
@@ -23,6 +23,7 @@
 	"github.com/apache/incubator-devlake/core/errors"
 	coreModels "github.com/apache/incubator-devlake/core/models"
 	"github.com/apache/incubator-devlake/core/models/domainlayer"
+	"github.com/apache/incubator-devlake/core/models/domainlayer/code"
 	"github.com/apache/incubator-devlake/core/models/domainlayer/devops"
 	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
 	"github.com/apache/incubator-devlake/core/plugin"
@@ -54,5 +55,13 @@
 		Name: connection.Name,
 	})
 
+	// add repos to scopes
+	scopes = append(scopes, &code.Repo{
+		DomainEntity: domainlayer.DomainEntity{
+			Id: fmt.Sprintf("%s:%d", "webhook", connection.ID),
+		},
+		Name: connection.Name,
+	})
+
 	return nil, scopes, nil
 }
diff --git a/backend/plugins/webhook/api/connection.go b/backend/plugins/webhook/api/connection.go
index 89ca884..16f027d 100644
--- a/backend/plugins/webhook/api/connection.go
+++ b/backend/plugins/webhook/api/connection.go
@@ -184,6 +184,7 @@
 	models.WebhookConnection
 	PostIssuesEndpoint             string             `json:"postIssuesEndpoint"`
 	CloseIssuesEndpoint            string             `json:"closeIssuesEndpoint"`
+	PostPullRequestsEndpoint       string             `json:"postPullRequestsEndpoint"`
 	PostPipelineTaskEndpoint       string             `json:"postPipelineTaskEndpoint"`
 	PostPipelineDeployTaskEndpoint string             `json:"postPipelineDeployTaskEndpoint"`
 	ClosePipelineEndpoint          string             `json:"closePipelineEndpoint"`
@@ -256,6 +257,7 @@
 	response := &WebhookConnectionResponse{WebhookConnection: *connection}
 	response.PostIssuesEndpoint = fmt.Sprintf(`/rest/plugins/webhook/connections/%d/issues`, connection.ID)
 	response.CloseIssuesEndpoint = fmt.Sprintf(`/rest/plugins/webhook/connections/%d/issue/:issueKey/close`, connection.ID)
+	response.PostPullRequestsEndpoint = fmt.Sprintf(`/rest/plugins/webhook/connections/%d/pull_requests`, connection.ID)
 	response.PostPipelineTaskEndpoint = fmt.Sprintf(`/rest/plugins/webhook/connections/%d/cicd_tasks`, connection.ID)
 	response.PostPipelineDeployTaskEndpoint = fmt.Sprintf(`/rest/plugins/webhook/connections/%d/deployments`, connection.ID)
 	response.ClosePipelineEndpoint = fmt.Sprintf(`/rest/plugins/webhook/connections/%d/cicd_pipeline/:pipelineName/finish`, connection.ID)
diff --git a/backend/plugins/webhook/api/pull_requests.go b/backend/plugins/webhook/api/pull_requests.go
new file mode 100644
index 0000000..a01bb6c
--- /dev/null
+++ b/backend/plugins/webhook/api/pull_requests.go
@@ -0,0 +1,176 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/log"
+
+	"github.com/apache/incubator-devlake/helpers/dbhelper"
+	"github.com/go-playground/validator/v10"
+
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/models/domainlayer"
+	"github.com/apache/incubator-devlake/core/models/domainlayer/code"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/plugins/webhook/models"
+)
+
+type WebhookPullRequestReq struct {
+	Id             string     `mapstructure:"id" validate:"required"`
+	BaseRepoId     string     `mapstructure:"baseRepoId"`
+	HeadRepoId     string     `mapstructure:"headRepoId"`
+	Status         string     `mapstructure:"status" validate:"omitempty,oneof=OPEN CLOSED MERGED"`
+	OriginalStatus string     `mapstructure:"originalStatus"`
+	Title          string     `mapstructure:"displayTitle" validate:"required"`
+	Description    string     `mapstructure:"description"`
+	Url            string     `mapstructure:"url"`
+	AuthorName     string     `mapstructure:"authorName"`
+	AuthorId       string     `mapstructure:"authorId"`
+	MergedByName   string     `mapstructure:"mergedByName"`
+	MergedById     string     `mapstructure:"mergedById"`
+	ParentPrId     string     `mapstructure:"parentPrId"`
+	PullRequestKey int        `mapstructure:"pullRequestKey" validate:"required"`
+	CreatedDate    time.Time  `mapstructure:"createdDate" validate:"required"`
+	MergedDate     *time.Time `mapstructure:"mergedDate"`
+	ClosedDate     *time.Time `mapstructure:"closedDate"`
+	Type           string     `mapstructure:"type"`
+	Component      string     `mapstructure:"component"`
+	MergeCommitSha string     `mapstructure:"mergeCommitSha"`
+	HeadRef        string     `mapstructure:"headRef"`
+	BaseRef        string     `mapstructure:"baseRef"`
+	BaseCommitSha  string     `mapstructure:"baseCommitSha"`
+	HeadCommitSha  string     `mapstructure:"headCommitSha"`
+	Additions      int        `mapstructure:"additions"`
+	Deletions      int        `mapstructure:"deletions"`
+	IsDraft        bool       `mapstructure:"isDraft"`
+}
+
+// PostPullRequests
+// @Summary create pull requests by webhook
+// @Description Create pull request by webhook.<br/>
+// @Description example1: {"id": "pr1","baseRepoId": "webhook:1","headRepoId": "repo_fork1","status": "MERGED","originalStatus": "OPEN","displayTitle": "Feature: Add new functionality","description": "This PR adds new features","url": "https://github.com/org/repo/pull/1","authorName": "johndoe","authorId": "johnd123","mergedByName": "janedoe","mergedById": "janed123","parentPrId": "","pullRequestKey": 1,"createdDate": "2025-02-20T16:17:36Z","mergedDate": "2025-02-20T17:17:36Z","closedDate": null,"type": "feature","component": "backend","mergeCommitSha": "bf0a79c57dff8f5f1f393de315ee5105a535e059","headRef": "repo_fork1:feature-branch","baseRef": "main","baseCommitSha": "e73325c2c9863f42ea25871cbfaeebcb8edcf604","headCommitSha": "b22f772f1197edfafd4cc5fe679a2d299ec12837","additions": 100,"deletions": 50,"isDraft": false}<br/>
+// @Description "baseRepoId" must be equal to "webhook:{connectionId}" for this to work correctly and calculate DORA metrics
+// @Tags plugins/webhook
+// @Param body body WebhookPullRequestReq true "json body"
+// @Success 200
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 403  {string} errcode.Error "Forbidden"
+// @Failure 500  {string} errcode.Error "Internal Error"
+// @Router /plugins/webhook/connections/:connectionId/pullrequests [POST]
+func PostPullRequests(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	connection := &models.WebhookConnection{}
+	err := connectionHelper.First(connection, input.Params)
+
+	return postPullRequests(input, connection, err)
+}
+
+// PostPullRequestsByName
+// @Summary create pull requests by webhook name
+// @Description Create pull request by webhook name.<br/>
+// @Description example1: {"id": "pr1","baseRepoId": "webhook:1","headRepoId": "repo_fork1","status": "MERGED","originalStatus": "OPEN","displayTitle": "Feature: Add new functionality","description": "This PR adds new features","url": "https://github.com/org/repo/pull/1","authorName": "johndoe","authorId": "johnd123","mergedByName": "janedoe","mergedById": "janed123","parentPrId": "","pullRequestKey": 1,"createdDate": "2025-02-20T16:17:36Z","mergedDate": "2025-02-20T17:17:36Z","closedDate": null,"type": "feature","component": "backend","mergeCommitSha": "bf0a79c57dff8f5f1f393de315ee5105a535e059","headRef": "repo_fork1:feature-branch","baseRef": "main","baseCommitSha": "e73325c2c9863f42ea25871cbfaeebcb8edcf604","headCommitSha": "b22f772f1197edfafd4cc5fe679a2d299ec12837","additions": 100,"deletions": 50,"isDraft": false}<br/>
+// @Description "baseRepoId" must be equal to "webhook:{connectionId}" for this to work correctly and calculate DORA metrics
+// @Tags plugins/webhook
+// @Param body body WebhookPullRequestReq true "json body"
+// @Success 200
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 403  {string} errcode.Error "Forbidden"
+// @Failure 500  {string} errcode.Error "Internal Error"
+// @Router /plugins/webhook/connections/by-name/:connectionName/pullrequests [POST]
+func PostPullRequestsByName(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
+	connection := &models.WebhookConnection{}
+	err := connectionHelper.FirstByName(connection, input.Params)
+
+	return postPullRequests(input, connection, err)
+}
+
+func postPullRequests(input *plugin.ApiResourceInput, connection *models.WebhookConnection, err errors.Error) (*plugin.ApiResourceOutput, errors.Error) {
+	if err != nil {
+		return nil, err
+	}
+	// get request
+	request := &WebhookPullRequestReq{}
+	err = api.DecodeMapStruct(input.Body, request, true)
+	if err != nil {
+		return &plugin.ApiResourceOutput{Body: err.Error(), Status: http.StatusBadRequest}, nil
+	}
+	// validate
+	vld = validator.New()
+	err = errors.Convert(vld.Struct(request))
+	if err != nil {
+		return nil, errors.BadInput.Wrap(vld.Struct(request), `input json error`)
+	}
+	txHelper := dbhelper.NewTxHelper(basicRes, &err)
+	defer txHelper.End()
+	tx := txHelper.Begin()
+	if err := CreatePullRequest(connection, request, tx, logger); err != nil {
+		logger.Error(err, "create pull requests")
+		return nil, err
+	}
+
+	return &plugin.ApiResourceOutput{Body: nil, Status: http.StatusOK}, nil
+}
+
+func CreatePullRequest(connection *models.WebhookConnection, request *WebhookPullRequestReq, tx dal.Transaction, logger log.Logger) errors.Error {
+	// validation
+	if request == nil {
+		return errors.BadInput.New("request body is nil")
+	}
+	// create a pull_request record
+	pullRequest := &code.PullRequest{
+		DomainEntity: domainlayer.DomainEntity{
+			Id: fmt.Sprintf("%s:%d:%d", "webhook", connection.ID, request.PullRequestKey),
+		},
+		BaseRepoId:     fmt.Sprintf("%s:%d", "webhook", connection.ID),
+		HeadRepoId:     request.HeadRepoId,
+		Status:         request.Status,
+		OriginalStatus: request.OriginalStatus,
+		Title:          request.Title,
+		Description:    request.Description,
+		Url:            request.Url,
+		AuthorName:     request.AuthorName,
+		AuthorId:       request.AuthorId,
+		MergedByName:   request.MergedByName,
+		MergedById:     request.MergedById,
+		ParentPrId:     request.ParentPrId,
+		PullRequestKey: request.PullRequestKey,
+		CreatedDate:    request.CreatedDate,
+		MergedDate:     request.MergedDate,
+		ClosedDate:     request.ClosedDate,
+		Type:           request.Type,
+		Component:      request.Component,
+		MergeCommitSha: request.MergeCommitSha,
+		HeadRef:        request.HeadRef,
+		BaseRef:        request.BaseRef,
+		BaseCommitSha:  request.BaseCommitSha,
+		HeadCommitSha:  request.HeadCommitSha,
+		Additions:      request.Additions,
+		Deletions:      request.Deletions,
+		IsDraft:        request.IsDraft,
+	}
+	if err := tx.CreateOrUpdate(pullRequest); err != nil {
+		logger.Error(err, "failed to save pull request")
+		return err
+	}
+	return nil
+}
diff --git a/backend/plugins/webhook/impl/impl.go b/backend/plugins/webhook/impl/impl.go
index c0d2e70..b969180 100644
--- a/backend/plugins/webhook/impl/impl.go
+++ b/backend/plugins/webhook/impl/impl.go
@@ -95,6 +95,9 @@
 		"connections/:connectionId/deployments": {
 			"POST": api.PostDeployments,
 		},
+		"connections/:connectionId/pull_requests": {
+			"POST": api.PostPullRequests,
+		},
 		"connections/:connectionId/issues": {
 			"POST": api.PostIssue,
 		},
@@ -104,6 +107,9 @@
 		":connectionId/deployments": {
 			"POST": api.PostDeployments,
 		},
+		":connectionId/pull_requests": {
+			"POST": api.PostPullRequests,
+		},
 		":connectionId/issues": {
 			"POST": api.PostIssue,
 		},
@@ -118,6 +124,9 @@
 		"connections/by-name/:connectionName/deployments": {
 			"POST": api.PostDeploymentsByName,
 		},
+		"connections/by-name/:connectionName/pull_requests": {
+			"POST": api.PostPullRequestsByName,
+		},
 		"connections/by-name/:connectionName/issues": {
 			"POST": api.PostIssueByName,
 		},
diff --git a/config-ui/src/features/connections/utils.ts b/config-ui/src/features/connections/utils.ts
index 781fb4f..e64e32d 100644
--- a/config-ui/src/features/connections/utils.ts
+++ b/config-ui/src/features/connections/utils.ts
@@ -50,6 +50,7 @@
     postIssuesEndpoint: connection.postIssuesEndpoint,
     closeIssuesEndpoint: connection.closeIssuesEndpoint,
     postPipelineDeployTaskEndpoint: connection.postPipelineDeployTaskEndpoint,
+    postPullRequestsEndpoint: connection.postPullRequestsEndpoint,
     apiKeyId: connection.apiKey.id,
   };
 };
diff --git a/config-ui/src/plugins/register/webhook/components/create-dialog.tsx b/config-ui/src/plugins/register/webhook/components/create-dialog.tsx
index 54ec50a..47f861b 100644
--- a/config-ui/src/plugins/register/webhook/components/create-dialog.tsx
+++ b/config-ui/src/plugins/register/webhook/components/create-dialog.tsx
@@ -44,6 +44,7 @@
     postIssuesEndpoint: '',
     closeIssuesEndpoint: '',
     postDeploymentsCurl: '',
+    postPullRequestsEndpoint: '',
     apiKey: '',
   });
 
@@ -55,7 +56,7 @@
     const [success, res] = await operator(
       async () => {
         const {
-          webhook: { id, postIssuesEndpoint, closeIssuesEndpoint, postPipelineDeployTaskEndpoint },
+          webhook: { id, postIssuesEndpoint, closeIssuesEndpoint, postPipelineDeployTaskEndpoint, postPullRequestsEndpoint },
           apiKey,
         } = await dispatch(addWebhook({ name })).unwrap();
 
@@ -65,6 +66,7 @@
           postIssuesEndpoint,
           closeIssuesEndpoint,
           postPipelineDeployTaskEndpoint,
+          postPullRequestsEndpoint,
         };
       },
       {
@@ -151,6 +153,17 @@
               .
             </p>
           </Block>
+          <Block title="Pull Requests">
+            <h5>Post to register a pull request</h5>
+            <CopyText content={record.postPullRequestsEndpoint} />
+            <p>
+              See the{' '}
+              <ExternalLink link="https://devlake.apache.org/docs/Plugins/webhook#pull_requests">
+                full payload schema
+              </ExternalLink>
+              .
+            </p>
+          </Block>
         </S.Wrapper>
       )}
     </Modal>
diff --git a/config-ui/src/plugins/register/webhook/components/utils.ts b/config-ui/src/plugins/register/webhook/components/utils.ts
index e417216..5892b33 100644
--- a/config-ui/src/plugins/register/webhook/components/utils.ts
+++ b/config-ui/src/plugins/register/webhook/components/utils.ts
@@ -20,9 +20,8 @@
 
 export const transformURI = (prefix: string, webhook: IWebhook, apiKey: string) => {
   return {
-    postIssuesEndpoint: `curl ${prefix}${webhook.postIssuesEndpoint} -X 'POST' -H 'Authorization: Bearer ${
-      apiKey ?? '{API_KEY}'
-    }' -d '{
+    postIssuesEndpoint: `curl ${prefix}${webhook.postIssuesEndpoint} -X 'POST' -H 'Authorization: Bearer ${apiKey ?? '{API_KEY}'
+      }' -d '{
       "issueKey":"DLK-1234",
       "title":"an incident from DLK",
       "type":"INCIDENT",
@@ -31,12 +30,10 @@
       "createdDate":"2020-01-01T12:00:00+00:00",
       "updatedDate":"2020-01-01T12:00:00+00:00"
     }'`,
-    closeIssuesEndpoint: `curl ${prefix}${webhook.closeIssuesEndpoint} -X 'POST' -H 'Authorization: Bearer ${
-      apiKey ?? '{API_KEY}'
-    }'`,
-    postDeploymentsCurl: `curl ${prefix}${webhook.postPipelineDeployTaskEndpoint} -X 'POST' -H 'Authorization: Bearer ${
-      apiKey ?? '{API_KEY}'
-    }' -d '{
+    closeIssuesEndpoint: `curl ${prefix}${webhook.closeIssuesEndpoint} -X 'POST' -H 'Authorization: Bearer ${apiKey ?? '{API_KEY}'
+      }'`,
+    postDeploymentsCurl: `curl ${prefix}${webhook.postPipelineDeployTaskEndpoint} -X 'POST' -H 'Authorization: Bearer ${apiKey ?? '{API_KEY}'
+      }' -d '{
       "id": "Required. This will be the unique ID of the deployment",
       "startedDate": "2023-01-01T12:00:00+00:00",
       "finishedDate": "2023-01-01T12:00:00+00:00",
@@ -53,5 +50,26 @@
         }
       ]
     }'`,
+    postPullRequestsEndpoint: `curl ${prefix}${webhook.postPullRequestsEndpoint} -X 'POST' -H 'Authorization: Bearer ${apiKey ?? '{API_KEY}'
+      }' -d '{
+      "id": "Required. This will be the unique ID of the pull request",
+      "baseRepoId": "your-repo-id",
+      "headRepoId": "your-repo-id",
+      "status": "MERGED",
+      "originalStatus": "OPEN",
+      "displayTitle": "Feature: Add new functionality",
+      "description": "This PR adds new features",
+      "url": "https://github.com/org/repo/pull/1",
+      "pullRequestKey": 1,
+      "createdDate": "2025-02-20T16:17:36Z",
+      "mergedDate": "2025-02-20T17:17:36Z",
+      "closedDate": null,
+      "mergeCommitSha": "bf0a79c57dff8f5f1f393de315ee5105a535e059",
+      "headRef": "your-branch-name",
+      "baseRef": "main",
+      "baseCommitSha": "e73325c2c9863f42ea25871cbfaeebcb8edcf604",
+      "headCommitSha": "b22f772f1197edfafd4cc5fe679a2d299ec12837",
+      "isDraft": false
+    }`,
   };
 };
diff --git a/config-ui/src/plugins/register/webhook/components/view-dialog.tsx b/config-ui/src/plugins/register/webhook/components/view-dialog.tsx
index 95750c0..a7bfd80 100644
--- a/config-ui/src/plugins/register/webhook/components/view-dialog.tsx
+++ b/config-ui/src/plugins/register/webhook/components/view-dialog.tsx
@@ -94,6 +94,17 @@
             .
           </p>
         </Block>
+        <Block title="Pull Requests">
+          <h5>Post to register/update a pull_request</h5>
+          <CopyText content={URI.postPullRequestsEndpoint} />
+          <p>
+            See the{' '}
+            <ExternalLink link="https://devlake.apache.org/docs/Plugins/webhook#pull_requests">
+              full payload schema
+            </ExternalLink>
+            .
+          </p>
+        </Block>
         <Block
           title="API Key"
           description="If you have forgotten your API key, you can revoke the previous key and generate a new one as a replacement."
diff --git a/config-ui/src/types/webhook.ts b/config-ui/src/types/webhook.ts
index 5ea4b89..0ea41ec 100644
--- a/config-ui/src/types/webhook.ts
+++ b/config-ui/src/types/webhook.ts
@@ -22,6 +22,7 @@
   postIssuesEndpoint: string;
   closeIssuesEndpoint: string;
   postPipelineDeployTaskEndpoint: string;
+  postPullRequestsEndpoint: string;
   apiKey: {
     id: number;
     apiKey: string;
@@ -34,5 +35,6 @@
   postIssuesEndpoint: string;
   closeIssuesEndpoint: string;
   postPipelineDeployTaskEndpoint: string;
+  postPullRequestsEndpoint: string;
   apiKeyId: number;
 }