Repository: google/go-github
Pull request #3379 (2024-12-10 00:05:12)

feat: Add custom_property events
custom_property and custom_property_values events are added.

Closes https://github.com/google/go-github/issues/3207

- [x] I have run `script/fmt.sh`
- [x] I have run `script/test.sh`
- [x] I have run `script/lint.sh`
- [x] I have run `script/generate.sh` because changes were detected 

google-cla[bot]: Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this [failed invocation](https://github.com/google/go-github/pull/3379/checks?check_run_id=34162892331) of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/google/go-github/pull/3379?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 92.30%. Comparing base [(`2b8c7fa`)](https://app.codecov.io/gh/google/go-github/commit/2b8c7fa86fcd1cb4a89a44c8e7b5a49fe4be8a45?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) to head [(`a309c6e`)](https://app.codecov.io/gh/google/go-github/commit/a309c6e821b53a27c1dc079970984a028a6039db?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).
> Report is 190 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3379      +/-   ##
==========================================
- Coverage   97.72%   92.30%   -5.42%     
==========================================
  Files         153      176      +23     
  Lines       13390    15031    +1641     
==========================================
+ Hits        13085    13874     +789     
- Misses        215     1064     +849     
- Partials       90       93       +3     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/google/go-github/pull/3379?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).

acouvreur: > Thank you, @acouvreur! One minor tweak, please, then we should be ready for a second LGTM+Approval from any other contributor to this repo before merging.

Done!

Patch:
diff --git a/AUTHORS b/AUTHORS
index bbbdc74b..6e3c5035 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -40,6 +40,7 @@ Alex Su <alexsu@17.media>
 Alex Unger <zyxancf@gmail.com>
 Alexander Harkness <me@bearbin.net>
 Alexey Alekhin <laughedelic@gmail.com>
+Alexis Couvreur <alexiscouvreur.pro@gmail.com>
 Alexis Gauthiez <alexis.gauthiez@gmail.com>
 Ali Farooq <ali.farooq0@pm.me>
 Alin Balutoiu <alinbalutoiu@gmail.com>
diff --git a/github/event_types.go b/github/event_types.go
index 373d59ba..5321a91f 100644
--- a/github/event_types.go
+++ b/github/event_types.go
@@ -132,6 +132,43 @@ type CreateEvent struct {
 	Installation *Installation `json:"installation,omitempty"`
 }
 
+// CustomPropertyEvent represents a created, deleted or updated custom property.
+// The Webhook event name is "custom_property".
+//
+// Note: this is related to custom property configuration at the enterprise or organization level.
+// See CustomPropertyValuesEvent for activity related to custom property values for a repository.
+//
+// GitHub API docs: https://docs.github.com/en/webhooks/webhook-events-and-payloads#custom_property
+type CustomPropertyEvent struct {
+	// Action possible values are: "created", "deleted", "updated".
+	Action     *string         `json:"action,omitempty"`
+	Definition *CustomProperty `json:"definition,omitempty"`
+
+	// The following fields are only populated by Webhook events.
+	Enterprise   *Enterprise   `json:"enterprise,omitempty"`
+	Installation *Installation `json:"installation,omitempty"`
+	Org          *Organization `json:"organization,omitempty"`
+	Sender       *User         `json:"sender,omitempty"`
+}
+
+// CustomPropertyValuesEvent represents an update to a custom property.
+// The Webhook event name is "custom_property_values".
+//
+// GitHub API docs: https://docs.github.com/en/webhooks/webhook-events-and-payloads#custom_property_values
+type CustomPropertyValuesEvent struct {
+	// Action possible values are: "updated".
+	Action            *string                `json:"action,omitempty"`
+	NewPropertyValues []*CustomPropertyValue `json:"new_property_values,omitempty"`
+	OldPropertyValues []*CustomPropertyValue `json:"old_property_values,omitempty"`
+
+	// The following fields are only populated by Webhook events.
+	Enterprise   *Enterprise   `json:"enterprise,omitempty"`
+	Installation *Installation `json:"installation,omitempty"`
+	Repo         *Repository   `json:"repository,omitempty"`
+	Org          *Organization `json:"organization,omitempty"`
+	Sender       *User         `json:"sender,omitempty"`
+}
+
 // DeleteEvent represents a deleted branch or tag.
 // The Webhook event name is "delete".
 //
diff --git a/github/github-accessors.go b/github/github-accessors.go
index db906dab..cc058414 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -5182,6 +5182,14 @@ func (c *CustomProperty) GetRequired() bool {
 	return *c.Required
 }
 
+// GetSourceType returns the SourceType field if it's non-nil, zero value otherwise.
+func (c *CustomProperty) GetSourceType() string {
+	if c == nil || c.SourceType == nil {
+		return ""
+	}
+	return *c.SourceType
+}
+
 // GetValuesEditableBy returns the ValuesEditableBy field if it's non-nil, zero value otherwise.
 func (c *CustomProperty) GetValuesEditableBy() string {
 	if c == nil || c.ValuesEditableBy == nil {
@@ -5190,6 +5198,102 @@ func (c *CustomProperty) GetValuesEditableBy() string {
 	return *c.ValuesEditableBy
 }
 
+// GetAction returns the Action field if it's non-nil, zero value otherwise.
+func (c *CustomPropertyEvent) GetAction() string {
+	if c == nil || c.Action == nil {
+		return ""
+	}
+	return *c.Action
+}
+
+// GetDefinition returns the Definition field.
+func (c *CustomPropertyEvent) GetDefinition() *CustomProperty {
+	if c == nil {
+		return nil
+	}
+	return c.Definition
+}
+
+// GetEnterprise returns the Enterprise field.
+func (c *CustomPropertyEvent) GetEnterprise() *Enterprise {
+	if c == nil {
+		return nil
+	}
+	return c.Enterprise
+}
+
+// GetInstallation returns the Installation field.
+func (c *CustomPropertyEvent) GetInstallation() *Installation {
+	if c == nil {
+		return nil
+	}
+	return c.Installation
+}
+
+// GetOrg returns the Org field.
+func (c *CustomPropertyEvent) GetOrg() *Organization {
+	if c == nil {
+		return nil
+	}
+	return c.Org
+}
+
+// GetSender returns the Sender field.
+func (c *CustomPropertyEvent) GetSender() *User {
+	if c == nil {
+		return nil
+	}
+	return c.Sender
+}
+
+// GetAction returns the Action field if it's non-nil, zero value otherwise.
+func (c *CustomPropertyValuesEvent) GetAction() string {
+	if c == nil || c.Action == nil {
+		return ""
+	}
+	return *c.Action
+}
+
+// GetEnterprise returns the Enterprise field.
+func (c *CustomPropertyValuesEvent) GetEnterprise() *Enterprise {
+	if c == nil {
+		return nil
+	}
+	return c.Enterprise
+}
+
+// GetInstallation returns the Installation field.
+func (c *CustomPropertyValuesEvent) GetInstallation() *Installation {
+	if c == nil {
+		return nil
+	}
+	return c.Installation
+}
+
+// GetOrg returns the Org field.
+func (c *CustomPropertyValuesEvent) GetOrg() *Organization {
+	if c == nil {
+		return nil
+	}
+	return c.Org
+}
+
+// GetRepo returns the Repo field.
+func (c *CustomPropertyValuesEvent) GetRepo() *Repository {
+	if c == nil {
+		return nil
+	}
+	return c.Repo
+}
+
+// GetSender returns the Sender field.
+func (c *CustomPropertyValuesEvent) GetSender() *User {
+	if c == nil {
+		return nil
+	}
+	return c.Sender
+}
+
 // GetBaseRole returns the BaseRole field if it's non-nil, zero value otherwise.
 func (c *CustomRepoRoles) GetBaseRole() string {
 	if c == nil || c.BaseRole == nil {
@@ -23382,6 +23486,14 @@ func (s *SecretScanningAlertUpdateOptions) GetResolution() string {
 	return *s.Resolution
 }
 
+// GetResolutionComment returns the ResolutionComment field if it's non-nil, zero value otherwise.
+func (s *SecretScanningAlertUpdateOptions) GetResolutionComment() string {
+	if s == nil || s.ResolutionComment == nil {
+		return ""
+	}
+	return *s.ResolutionComment
+}
+
 // GetStatus returns the Status field if it's non-nil, zero value otherwise.
 func (s *SecretScanningPushProtection) GetStatus() string {
 	if s == nil || s.Status == nil {
diff --git a/github/messages.go b/github/messages.go
index 608c557c..3df4f3a3 100644
--- a/github/messages.go
+++ b/github/messages.go
@@ -54,6 +54,8 @@ var (
 		"commit_comment":                  &CommitCommentEvent{},
 		"content_reference":               &ContentReferenceEvent{},
 		"create":                          &CreateEvent{},
+		"custom_property":                 &CustomPropertyEvent{},
+		"custom_property_values":          &CustomPropertyValuesEvent{},
 		"delete":                          &DeleteEvent{},
 		"dependabot_alert":                &DependabotAlertEvent{},
 		"deploy_key":                      &DeployKeyEvent{},
diff --git a/github/orgs_properties.go b/github/orgs_properties.go
index d4fe5dfa..f59d9f46 100644
--- a/github/orgs_properties.go
+++ b/github/orgs_properties.go
@@ -17,7 +17,9 @@ type CustomProperty struct {
 	// PropertyName is required for most endpoints except when calling CreateOrUpdateCustomProperty;
 	// where this is sent in the path and thus can be omitted.
 	PropertyName *string `json:"property_name,omitempty"`
-	// The type of the value for the property. Can be one of: string, single_select.
+	// SourceType is the source type of the property where it has been created. Can be one of: organization, enterprise.
+	SourceType *string `json:"source_type,omitempty"`
+	// The type of the value for the property. Can be one of: string, single_select, multi_select, true_false.
 	ValueType string `json:"value_type"`
 	// Whether the property is required.
 	Required *bool `json:"required,omitempty"`
diff --git a/github/secret_scanning.go b/github/secret_scanning.go
index 27449262..fc0fe0cd 100644
--- a/github/secret_scanning.go
+++ b/github/secret_scanning.go
@@ -89,6 +89,9 @@ type SecretScanningAlertUpdateOptions struct {
 	// Required when the state is "resolved" and represents the reason for resolving the alert.
 	// Can be one of: "false_positive", "wont_fix", "revoked", or "used_in_tests".
 	Resolution *string `json:"resolution,omitempty"`
+
+	// An optional comment when closing an alert.
+	ResolutionComment *string `json:"resolution_comment,omitempty"`
 }
 
 // ListAlertsForEnterprise lists secret scanning alerts for eligible repositories in an enterprise, from newest to oldest.


Test patch:
diff --git a/github/event_types_test.go b/github/event_types_test.go
index b09b9279..2288c04c 100644
--- a/github/event_types_test.go
+++ b/github/event_types_test.go
@@ -14995,6 +14995,500 @@ func TestCreateEvent_Marshal(t *testing.T) {
 	testJSONMarshal(t, r, want)
 }
 
+func TestCustomPropertyEvent_Marshal(t *testing.T) {
+	t.Parallel()
+	testJSONMarshal(t, &CustomPropertyEvent{}, "{}")
+
+	r := &CustomPropertyEvent{
+		Action: String("created"),
+		Definition: &CustomProperty{
+			PropertyName:     String("name"),
+			ValueType:        "single_select",
+			SourceType:       String("enterprise"),
+			Required:         Bool(true),
+			DefaultValue:     String("production"),
+			Description:      String("Prod or dev environment"),
+			AllowedValues:    []string{"production", "development"},
+			ValuesEditableBy: String("org_actors"),
+		},
+		Sender: &User{
+			Login:     String("l"),
+			ID:        Int64(1),
+			NodeID:    String("n"),
+			URL:       String("u"),
+			ReposURL:  String("r"),
+			EventsURL: String("e"),
+			AvatarURL: String("a"),
+		},
+		Installation: &Installation{
+			ID:       Int64(1),
+			NodeID:   String("nid"),
+			AppID:    Int64(1),
+			AppSlug:  String("as"),
+			TargetID: Int64(1),
+			Account: &User{
+				Login:           String("l"),
+				ID:              Int64(1),
+				URL:             String("u"),
+				AvatarURL:       String("a"),
+				GravatarID:      String("g"),
+				Name:            String("n"),
+				Company:         String("c"),
+				Blog:            String("b"),
+				Location:        String("l"),
+				Email:           String("e"),
+				Hireable:        Bool(true),
+				Bio:             String("b"),
+				TwitterUsername: String("t"),
+				PublicRepos:     Int(1),
+				Followers:       Int(1),
+				Following:       Int(1),
+				CreatedAt:       &Timestamp{referenceTime},
+				SuspendedAt:     &Timestamp{referenceTime},
+			},
+			AccessTokensURL:     String("atu"),
+			RepositoriesURL:     String("ru"),
+			HTMLURL:             String("hu"),
+			TargetType:          String("tt"),
+			SingleFileName:      String("sfn"),
+			RepositorySelection: String("rs"),
+			Events:              []string{"e"},
+			SingleFilePaths:     []string{"s"},
+			Permissions: &InstallationPermissions{
+				Actions:                       String("a"),
+				Administration:                String("ad"),
+				Checks:                        String("c"),
+				Contents:                      String("co"),
+				ContentReferences:             String("cr"),
+				Deployments:                   String("d"),
+				Environments:                  String("e"),
+				Issues:                        String("i"),
+				Metadata:                      String("md"),
+				Members:                       String("m"),
+				OrganizationAdministration:    String("oa"),
+				OrganizationHooks:             String("oh"),
+				OrganizationPlan:              String("op"),
+				OrganizationPreReceiveHooks:   String("opr"),
+				OrganizationProjects:          String("op"),
+				OrganizationSecrets:           String("os"),
+				OrganizationSelfHostedRunners: String("osh"),
+				OrganizationUserBlocking:      String("oub"),
+				Packages:                      String("pkg"),
+				Pages:                         String("pg"),
+				PullRequests:                  String("pr"),
+				RepositoryHooks:               String("rh"),
+				RepositoryProjects:            String("rp"),
+				RepositoryPreReceiveHooks:     String("rprh"),
+				Secrets:                       String("s"),
+				SecretScanningAlerts:          String("ssa"),
+				SecurityEvents:                String("se"),
+				SingleFile:                    String("sf"),
+				Statuses:                      String("s"),
+				TeamDiscussions:               String("td"),
+				VulnerabilityAlerts:           String("va"),
+				Workflows:                     String("w"),
+			},
+			CreatedAt:              &Timestamp{referenceTime},
+			UpdatedAt:              &Timestamp{referenceTime},
+			HasMultipleSingleFiles: Bool(false),
+			SuspendedBy: &User{
+				Login:           String("l"),
+				ID:              Int64(1),
+				URL:             String("u"),
+				AvatarURL:       String("a"),
+				GravatarID:      String("g"),
+				Name:            String("n"),
+				Company:         String("c"),
+				Blog:            String("b"),
+				Location:        String("l"),
+				Email:           String("e"),
+				Hireable:        Bool(true),
+				Bio:             String("b"),
+				TwitterUsername: String("t"),
+				PublicRepos:     Int(1),
+				Followers:       Int(1),
+				Following:       Int(1),
+				CreatedAt:       &Timestamp{referenceTime},
+				SuspendedAt:     &Timestamp{referenceTime},
+			},
+			SuspendedAt: &Timestamp{referenceTime},
+		},
+	}
+
+	want := `{
+		"action": "created",
+		"definition": {
+			"property_name": "name",
+          	"source_type": "enterprise",
+          	"value_type": "single_select",
+          	"required": true,
+          	"default_value": "production",
+          	"description": "Prod or dev environment",
+          	"allowed_values": [
+            	"production",
+            	"development"
+          	],
+          	"values_editable_by": "org_actors"
+        },
+		"repository": {
+			"id": 1,
+			"name": "n",
+			"url": "s"
+		},
+		"sender": {
+			"login": "l",
+			"id": 1,
+			"node_id": "n",
+			"avatar_url": "a",
+			"url": "u",
+			"events_url": "e",
+			"repos_url": "r"
+		},
+		"installation": {
+			"id": 1,
+			"node_id": "nid",
+			"app_id": 1,
+			"app_slug": "as",
+			"target_id": 1,
+			"account": {
+				"login": "l",
+				"id": 1,
+				"avatar_url": "a",
+				"gravatar_id": "g",
+				"name": "n",
+				"company": "c",
+				"blog": "b",
+				"location": "l",
+				"email": "e",
+				"hireable": true,
+				"bio": "b",
+				"twitter_username": "t",
+				"public_repos": 1,
+				"followers": 1,
+				"following": 1,
+				"created_at": ` + referenceTimeStr + `,
+				"suspended_at": ` + referenceTimeStr + `,
+				"url": "u"
+			},
+			"access_tokens_url": "atu",
+			"repositories_url": "ru",
+			"html_url": "hu",
+			"target_type": "tt",
+			"single_file_name": "sfn",
+			"repository_selection": "rs",
+			"events": [
+				"e"
+			],
+			"single_file_paths": [
+				"s"
+			],
+			"permissions": {
+				"actions": "a",
+				"administration": "ad",
+				"checks": "c",
+				"contents": "co",
+				"content_references": "cr",
+				"deployments": "d",
+				"environments": "e",
+				"issues": "i",
+				"metadata": "md",
+				"members": "m",
+				"organization_administration": "oa",
+				"organization_hooks": "oh",
+				"organization_plan": "op",
+				"organization_pre_receive_hooks": "opr",
+				"organization_projects": "op",
+				"organization_secrets": "os",
+				"organization_self_hosted_runners": "osh",
+				"organization_user_blocking": "oub",
+				"packages": "pkg",
+				"pages": "pg",
+				"pull_requests": "pr",
+				"repository_hooks": "rh",
+				"repository_projects": "rp",
+				"repository_pre_receive_hooks": "rprh",
+				"secrets": "s",
+				"secret_scanning_alerts": "ssa",
+				"security_events": "se",
+				"single_file": "sf",
+				"statuses": "s",
+				"team_discussions": "td",
+				"vulnerability_alerts": "va",
+				"workflows": "w"
+			},
+			"created_at": ` + referenceTimeStr + `,
+			"updated_at": ` + referenceTimeStr + `,
+			"has_multiple_single_files": false,
+			"suspended_by": {
+				"login": "l",
+				"id": 1,
+				"avatar_url": "a",
+				"gravatar_id": "g",
+				"name": "n",
+				"company": "c",
+				"blog": "b",
+				"location": "l",
+				"email": "e",
+				"hireable": true,
+				"bio": "b",
+				"twitter_username": "t",
+				"public_repos": 1,
+				"followers": 1,
+				"following": 1,
+				"created_at": ` + referenceTimeStr + `,
+				"suspended_at": ` + referenceTimeStr + `,
+				"url": "u"
+			},
+			"suspended_at": ` + referenceTimeStr + `
+		}
+	}`
+
+	testJSONMarshal(t, r, want)
+}
+
+func TestCustomPropertyValuesEvent_Marshal(t *testing.T) {
+	t.Parallel()
+	testJSONMarshal(t, &CustomPropertyValuesEvent{}, "{}")
+
+	r := &CustomPropertyValuesEvent{
+		Action: String("updated"),
+		NewPropertyValues: []*CustomPropertyValue{
+			{
+				PropertyName: "environment",
+				Value:        "production",
+			},
+		},
+		OldPropertyValues: []*CustomPropertyValue{
+			{
+				PropertyName: "environment",
+				Value:        "staging",
+			},
+		},
+		Sender: &User{
+			Login:     String("l"),
+			ID:        Int64(1),
+			NodeID:    String("n"),
+			URL:       String("u"),
+			ReposURL:  String("r"),
+			EventsURL: String("e"),
+			AvatarURL: String("a"),
+		},
+		Installation: &Installation{
+			ID:       Int64(1),
+			NodeID:   String("nid"),
+			AppID:    Int64(1),
+			AppSlug:  String("as"),
+			TargetID: Int64(1),
+			Account: &User{
+				Login:           String("l"),
+				ID:              Int64(1),
+				URL:             String("u"),
+				AvatarURL:       String("a"),
+				GravatarID:      String("g"),
+				Name:            String("n"),
+				Company:         String("c"),
+				Blog:            String("b"),
+				Location:        String("l"),
+				Email:           String("e"),
+				Hireable:        Bool(true),
+				Bio:             String("b"),
+				TwitterUsername: String("t"),
+				PublicRepos:     Int(1),
+				Followers:       Int(1),
+				Following:       Int(1),
+				CreatedAt:       &Timestamp{referenceTime},
+				SuspendedAt:     &Timestamp{referenceTime},
+			},
+			AccessTokensURL:     String("atu"),
+			RepositoriesURL:     String("ru"),
+			HTMLURL:             String("hu"),
+			TargetType:          String("tt"),
+			SingleFileName:      String("sfn"),
+			RepositorySelection: String("rs"),
+			Events:              []string{"e"},
+			SingleFilePaths:     []string{"s"},
+			Permissions: &InstallationPermissions{
+				Actions:                       String("a"),
+				Administration:                String("ad"),
+				Checks:                        String("c"),
+				Contents:                      String("co"),
+				ContentReferences:             String("cr"),
+				Deployments:                   String("d"),
+				Environments:                  String("e"),
+				Issues:                        String("i"),
+				Metadata:                      String("md"),
+				Members:                       String("m"),
+				OrganizationAdministration:    String("oa"),
+				OrganizationHooks:             String("oh"),
+				OrganizationPlan:              String("op"),
+				OrganizationPreReceiveHooks:   String("opr"),
+				OrganizationProjects:          String("op"),
+				OrganizationSecrets:           String("os"),
+				OrganizationSelfHostedRunners: String("osh"),
+				OrganizationUserBlocking:      String("oub"),
+				Packages:                      String("pkg"),
+				Pages:                         String("pg"),
+				PullRequests:                  String("pr"),
+				RepositoryHooks:               String("rh"),
+				RepositoryProjects:            String("rp"),
+				RepositoryPreReceiveHooks:     String("rprh"),
+				Secrets:                       String("s"),
+				SecretScanningAlerts:          String("ssa"),
+				SecurityEvents:                String("se"),
+				SingleFile:                    String("sf"),
+				Statuses:                      String("s"),
+				TeamDiscussions:               String("td"),
+				VulnerabilityAlerts:           String("va"),
+				Workflows:                     String("w"),
+			},
+			CreatedAt:              &Timestamp{referenceTime},
+			UpdatedAt:              &Timestamp{referenceTime},
+			HasMultipleSingleFiles: Bool(false),
+			SuspendedBy: &User{
+				Login:           String("l"),
+				ID:              Int64(1),
+				URL:             String("u"),
+				AvatarURL:       String("a"),
+				GravatarID:      String("g"),
+				Name:            String("n"),
+				Company:         String("c"),
+				Blog:            String("b"),
+				Location:        String("l"),
+				Email:           String("e"),
+				Hireable:        Bool(true),
+				Bio:             String("b"),
+				TwitterUsername: String("t"),
+				PublicRepos:     Int(1),
+				Followers:       Int(1),
+				Following:       Int(1),
+				CreatedAt:       &Timestamp{referenceTime},
+				SuspendedAt:     &Timestamp{referenceTime},
+			},
+			SuspendedAt: &Timestamp{referenceTime},
+		},
+	}
+
+	want := `{
+		"action": "updated",
+		"new_property_values": [{
+			"property_name": "environment",
+			"value": "production"
+        }],
+		"old_property_values": [{
+			"property_name": "environment",
+			"value": "staging"
+        }],
+		"sender": {
+			"login": "l",
+			"id": 1,
+			"node_id": "n",
+			"avatar_url": "a",
+			"url": "u",
+			"events_url": "e",
+			"repos_url": "r"
+		},
+		"installation": {
+			"id": 1,
+			"node_id": "nid",
+			"app_id": 1,
+			"app_slug": "as",
+			"target_id": 1,
+			"account": {
+				"login": "l",
+				"id": 1,
+				"avatar_url": "a",
+				"gravatar_id": "g",
+				"name": "n",
+				"company": "c",
+				"blog": "b",
+				"location": "l",
+				"email": "e",
+				"hireable": true,
+				"bio": "b",
+				"twitter_username": "t",
+				"public_repos": 1,
+				"followers": 1,
+				"following": 1,
+				"created_at": ` + referenceTimeStr + `,
+				"suspended_at": ` + referenceTimeStr + `,
+				"url": "u"
+			},
+			"access_tokens_url": "atu",
+			"repositories_url": "ru",
+			"html_url": "hu",
+			"target_type": "tt",
+			"single_file_name": "sfn",
+			"repository_selection": "rs",
+			"events": [
+				"e"
+			],
+			"single_file_paths": [
+				"s"
+			],
+			"permissions": {
+				"actions": "a",
+				"administration": "ad",
+				"checks": "c",
+				"contents": "co",
+				"content_references": "cr",
+				"deployments": "d",
+				"environments": "e",
+				"issues": "i",
+				"metadata": "md",
+				"members": "m",
+				"organization_administration": "oa",
+				"organization_hooks": "oh",
+				"organization_plan": "op",
+				"organization_pre_receive_hooks": "opr",
+				"organization_projects": "op",
+				"organization_secrets": "os",
+				"organization_self_hosted_runners": "osh",
+				"organization_user_blocking": "oub",
+				"packages": "pkg",
+				"pages": "pg",
+				"pull_requests": "pr",
+				"repository_hooks": "rh",
+				"repository_projects": "rp",
+				"repository_pre_receive_hooks": "rprh",
+				"secrets": "s",
+				"secret_scanning_alerts": "ssa",
+				"security_events": "se",
+				"single_file": "sf",
+				"statuses": "s",
+				"team_discussions": "td",
+				"vulnerability_alerts": "va",
+				"workflows": "w"
+			},
+			"created_at": ` + referenceTimeStr + `,
+			"updated_at": ` + referenceTimeStr + `,
+			"has_multiple_single_files": false,
+			"suspended_by": {
+				"login": "l",
+				"id": 1,
+				"avatar_url": "a",
+				"gravatar_id": "g",
+				"name": "n",
+				"company": "c",
+				"blog": "b",
+				"location": "l",
+				"email": "e",
+				"hireable": true,
+				"bio": "b",
+				"twitter_username": "t",
+				"public_repos": 1,
+				"followers": 1,
+				"following": 1,
+				"created_at": ` + referenceTimeStr + `,
+				"suspended_at": ` + referenceTimeStr + `,
+				"url": "u"
+			},
+			"suspended_at": ` + referenceTimeStr + `
+		}
+	}`
+
+	testJSONMarshal(t, r, want)
+}
+
 func TestDeleteEvent_Marshal(t *testing.T) {
 	t.Parallel()
 	testJSONMarshal(t, &DeleteEvent{}, "{}")
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 67b833a6..71cef92f 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -6737,6 +6737,17 @@ func TestCustomProperty_GetRequired(tt *testing.T) {
 	c.GetRequired()
 }
 
+func TestCustomProperty_GetSourceType(tt *testing.T) {
+	tt.Parallel()
+	var zeroValue string
+	c := &CustomProperty{SourceType: &zeroValue}
+	c.GetSourceType()
+	c = &CustomProperty{}
+	c.GetSourceType()
+	c = nil
+	c.GetSourceType()
+}
+
 func TestCustomProperty_GetValuesEditableBy(tt *testing.T) {
 	tt.Parallel()
 	var zeroValue string
@@ -6748,6 +6759,108 @@ func TestCustomProperty_GetValuesEditableBy(tt *testing.T) {
 	c.GetValuesEditableBy()
 }
 
+func TestCustomPropertyEvent_GetAction(tt *testing.T) {
+	tt.Parallel()
+	var zeroValue string
+	c := &CustomPropertyEvent{Action: &zeroValue}
+	c.GetAction()
+	c = &CustomPropertyEvent{}
+	c.GetAction()
+	c = nil
+	c.GetAction()
+}
+
+func TestCustomPropertyEvent_GetDefinition(tt *testing.T) {
+	tt.Parallel()
+	c := &CustomPropertyEvent{}
+	c.GetDefinition()
+	c = nil
+	c.GetDefinition()
+}
+
+func TestCustomPropertyEvent_GetEnterprise(tt *testing.T) {
+	tt.Parallel()
+	c := &CustomPropertyEvent{}
+	c.GetEnterprise()
+	c = nil
+	c.GetEnterprise()
+}
+
+func TestCustomPropertyEvent_GetInstallation(tt *testing.T) {
+	tt.Parallel()
+	c := &CustomPropertyEvent{}
+	c.GetInstallation()
+	c = nil
+	c.GetInstallation()
+}
+
+func TestCustomPropertyEvent_GetOrg(tt *testing.T) {
+	tt.Parallel()
+	c := &CustomPropertyEvent{}
+	c.GetOrg()
+	c = nil
+	c.GetOrg()
+}
+
+func TestCustomPropertyEvent_GetSender(tt *testing.T) {
+	tt.Parallel()
+	c := &CustomPropertyEvent{}
+	c.GetSender()
+	c = nil
+	c.GetSender()
+}
+
+func TestCustomPropertyValuesEvent_GetAction(tt *testing.T) {
+	tt.Parallel()
+	var zeroValue string
+	c := &CustomPropertyValuesEvent{Action: &zeroValue}
+	c.GetAction()
+	c = &CustomPropertyValuesEvent{}
+	c.GetAction()
+	c = nil
+	c.GetAction()
+}
+
+func TestCustomPropertyValuesEvent_GetEnterprise(tt *testing.T) {
+	tt.Parallel()
+	c := &CustomPropertyValuesEvent{}
+	c.GetEnterprise()
+	c = nil
+	c.GetEnterprise()
+}
+
+func TestCustomPropertyValuesEvent_GetInstallation(tt *testing.T) {
+	tt.Parallel()
+	c := &CustomPropertyValuesEvent{}
+	c.GetInstallation()
+	c = nil
+	c.GetInstallation()
+}
+
+func TestCustomPropertyValuesEvent_GetOrg(tt *testing.T) {
+	tt.Parallel()
+	c := &CustomPropertyValuesEvent{}
+	c.GetOrg()
+	c = nil
+	c.GetOrg()
+}
+
+func TestCustomPropertyValuesEvent_GetRepo(tt *testing.T) {
+	tt.Parallel()
+	c := &CustomPropertyValuesEvent{}
+	c.GetRepo()
+	c = nil
+	c.GetRepo()
+}
+
+func TestCustomPropertyValuesEvent_GetSender(tt *testing.T) {
+	tt.Parallel()
+	c := &CustomPropertyValuesEvent{}
+	c.GetSender()
+	c = nil
+	c.GetSender()
+}
+
 func TestCustomRepoRoles_GetBaseRole(tt *testing.T) {
 	tt.Parallel()
 	var zeroValue string
@@ -29986,6 +30099,17 @@ func TestSecretScanningAlertUpdateOptions_GetResolution(tt *testing.T) {
 	s.GetResolution()
 }
 
+func TestSecretScanningAlertUpdateOptions_GetResolutionComment(tt *testing.T) {
+	tt.Parallel()
+	var zeroValue string
+	s := &SecretScanningAlertUpdateOptions{ResolutionComment: &zeroValue}
+	s.GetResolutionComment()
+	s = &SecretScanningAlertUpdateOptions{}
+	s.GetResolutionComment()
+	s = nil
+	s.GetResolutionComment()
+}
+
 func TestSecretScanningPushProtection_GetStatus(tt *testing.T) {
 	tt.Parallel()
 	var zeroValue string
diff --git a/github/messages_test.go b/github/messages_test.go
index be325d50..e5a5327c 100644
--- a/github/messages_test.go
+++ b/github/messages_test.go
@@ -296,6 +296,14 @@ func TestParseWebHook(t *testing.T) {
 			payload:     &CreateEvent{},
 			messageType: "create",
 		},
+		{
+			payload:     &CustomPropertyEvent{},
+			messageType: "custom_property",
+		},
+		{
+			payload:     &CustomPropertyValuesEvent{},
+			messageType: "custom_property_values",
+		},
 		{
 			payload:     &DeleteEvent{},
 			messageType: "delete",
diff --git a/github/secret_scanning_test.go b/github/secret_scanning_test.go
index 6bb57ffe..de3afb7c 100644
--- a/github/secret_scanning_test.go
+++ b/github/secret_scanning_test.go
@@ -359,7 +359,7 @@ func TestSecretScanningService_UpdateAlert(t *testing.T) {
 		v := new(SecretScanningAlertUpdateOptions)
 		assertNilError(t, json.NewDecoder(r.Body).Decode(v))
 
-		want := &SecretScanningAlertUpdateOptions{State: "resolved", Resolution: String("used_in_tests")}
+		want := &SecretScanningAlertUpdateOptions{State: "resolved", Resolution: String("used_in_tests"), ResolutionComment: String("resolution comment")}
 
 		if !cmp.Equal(v, want) {
 			t.Errorf("Request body = %+v, want %+v", v, want)
@@ -373,6 +373,7 @@ func TestSecretScanningService_UpdateAlert(t *testing.T) {
 			"locations_url": "https://api.github.com/repos/o/r/secret-scanning/alerts/1/locations",
 			"state": "resolved",
 			"resolution": "used_in_tests",
+			"resolution_comment": "resolution comment",
 			"resolved_at": "1996-06-20T00:00:00Z",
 			"resolved_by": null,
 			"secret_type": "mailchimp_api_key",
@@ -381,7 +382,7 @@ func TestSecretScanningService_UpdateAlert(t *testing.T) {
 	})
 
 	ctx := context.Background()
-	opts := &SecretScanningAlertUpdateOptions{State: "resolved", Resolution: String("used_in_tests")}
+	opts := &SecretScanningAlertUpdateOptions{State: "resolved", Resolution: String("used_in_tests"), ResolutionComment: String("resolution comment")}
 
 	alert, _, err := client.SecretScanning.UpdateAlert(ctx, "o", "r", 1, opts)
 	if err != nil {
@@ -390,17 +391,18 @@ func TestSecretScanningService_UpdateAlert(t *testing.T) {
 
 	date := Timestamp{time.Date(1996, time.June, 20, 00, 00, 00, 0, time.UTC)}
 	want := &SecretScanningAlert{
-		Number:       Int(1),
-		CreatedAt:    &date,
-		URL:          String("https://api.github.com/repos/o/r/secret-scanning/alerts/1"),
-		HTMLURL:      String("https://github.com/o/r/security/secret-scanning/1"),
-		LocationsURL: String("https://api.github.com/repos/o/r/secret-scanning/alerts/1/locations"),
-		State:        String("resolved"),
-		Resolution:   String("used_in_tests"),
-		ResolvedAt:   &date,
-		ResolvedBy:   nil,
-		SecretType:   String("mailchimp_api_key"),
-		Secret:       String("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-us2"),
+		Number:            Int(1),
+		CreatedAt:         &date,
+		URL:               String("https://api.github.com/repos/o/r/secret-scanning/alerts/1"),
+		HTMLURL:           String("https://github.com/o/r/security/secret-scanning/1"),
+		LocationsURL:      String("https://api.github.com/repos/o/r/secret-scanning/alerts/1/locations"),
+		State:             String("resolved"),
+		Resolution:        String("used_in_tests"),
+		ResolutionComment: String("resolution comment"),
+		ResolvedAt:        &date,
+		ResolvedBy:        nil,
+		SecretType:        String("mailchimp_api_key"),
+		Secret:            String("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-us2"),
 	}
 
 	if !cmp.Equal(alert, want) {