Repository: google/go-github (go)

Issue:
Webhook event type `pull_request_review_thread` missing
It seems like GitHub is sending the webhook event type `pull_request_review_thread` when someone resolves a comment in a PR.

Webhook event schema can be seen here: https://github.com/octokit/webhooks/blob/d93eebf4c48dc3c36f9c7ceffab356fa444bf9fc/payload-schemas/api.github.com/pull_request_review_thread/resolved.schema.json

The event doesn't seem to be documented in [Webhook events and payload](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request_review_thread) though.


Pull request:
Add support for the pull request review thread event
Fixes #2324


Patch:
diff --git a/github/event.go b/github/event.go
index 9a39e2e9..5c103659 100644
--- a/github/event.go
+++ b/github/event.go
@@ -102,6 +102,8 @@ func (e *Event) ParsePayload() (payload interface{}, err error) {
 		payload = &PullRequestReviewEvent{}
 	case "PullRequestReviewCommentEvent":
 		payload = &PullRequestReviewCommentEvent{}
+	case "PullRequestReviewThreadEvent":
+		payload = &PullRequestReviewThreadEvent{}
 	case "PullRequestTargetEvent":
 		payload = &PullRequestTargetEvent{}
 	case "PushEvent":
diff --git a/github/event_types.go b/github/event_types.go
index 71baac88..7cfba9ab 100644
--- a/github/event_types.go
+++ b/github/event_types.go
@@ -830,6 +830,24 @@ type PullRequestReviewCommentEvent struct {
 	Installation *Installation `json:"installation,omitempty"`
 }
 
+// PullRequestReviewThreadEvent is triggered when a comment made as part of a
+// review of a pull request is marked resolved or unresolved.
+// The Webhook event name is "pull_request_review_thread".
+//
+// GitHub API docs: https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#pull_request_review_thread
+type PullRequestReviewThreadEvent struct {
+	// Action is the action that was performed on the comment.
+	// Possible values are: "resolved", "unresolved".
+	Action      *string            `json:"action,omitempty"`
+	Thread      *PullRequestThread `json:"thread,omitempty"`
+	PullRequest *PullRequest       `json:"pull_request,omitempty"`
+
+	// The following fields are only populated by Webhook events.
+	Repo         *Repository   `json:"repository,omitempty"`
+	Sender       *User         `json:"sender,omitempty"`
+	Installation *Installation `json:"installation,omitempty"`
+}
+
 // PullRequestTargetEvent is triggered when a pull request is assigned, unassigned, labeled,
 // unlabeled, opened, edited, closed, reopened, synchronize, ready_for_review,
 // locked, unlocked, a pull request review is requested, or a review request is removed.
diff --git a/github/gen-accessors.go b/github/gen-accessors.go
index 58ac3eb1..d8136d2c 100644
--- a/github/gen-accessors.go
+++ b/github/gen-accessors.go
@@ -186,10 +186,18 @@ func (t *templateData) dump() error {
 		}
 
 		logf("Writing %v...", filename)
-		if err := ioutil.WriteFile(filename, clean, 0644); err != nil {
+		if err := os.Chmod(filename, 0644); err != nil {
+			return fmt.Errorf("os.Chmod(%q, 0644): %v", filename, err)
+		}
+
+		if err := ioutil.WriteFile(filename, clean, 0444); err != nil {
 			return err
 		}
 
+		if err := os.Chmod(filename, 0444); err != nil {
+			return fmt.Errorf("os.Chmod(%q, 0444): %v", filename, err)
+		}
+
 		return nil
 	}
 
@@ -329,6 +337,8 @@ const source = `// Copyright {{.Year}} The go-github AUTHORS. All rights reserve
 // license that can be found in the LICENSE file.
 
 // Code generated by gen-accessors; DO NOT EDIT.
+// Instead, please run "go generate ./..." as described here:
+// https://github.com/google/go-github/blob/master/CONTRIBUTING.md#submitting-a-patch
 
 package {{.Package}}
 {{with .Imports}}
@@ -373,6 +383,8 @@ const test = `// Copyright {{.Year}} The go-github AUTHORS. All rights reserved.
 // license that can be found in the LICENSE file.
 
 // Code generated by gen-accessors; DO NOT EDIT.
+// Instead, please run "go generate ./..." as described here:
+// https://github.com/google/go-github/blob/master/CONTRIBUTING.md#submitting-a-patch
 
 package {{.Package}}
 {{with .Imports}}
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 26f06ea8..724e67f0 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -4,6 +4,8 @@
 // license that can be found in the LICENSE file.
 
 // Code generated by gen-accessors; DO NOT EDIT.
+// Instead, please run "go generate ./..." as described here:
+// https://github.com/google/go-github/blob/master/CONTRIBUTING.md#submitting-a-patch
 
 package github
 
@@ -12820,6 +12822,54 @@ func (p *PullRequestReviewsEnforcementUpdate) GetRequireCodeOwnerReviews() bool
 	return *p.RequireCodeOwnerReviews
 }
 
+// GetAction returns the Action field if it's non-nil, zero value otherwise.
+func (p *PullRequestReviewThreadEvent) GetAction() string {
+	if p == nil || p.Action == nil {
+		return ""
+	}
+	return *p.Action
+}
+
+// GetInstallation returns the Installation field.
+func (p *PullRequestReviewThreadEvent) GetInstallation() *Installation {
+	if p == nil {
+		return nil
+	}
+	return p.Installation
+}
+
+// GetPullRequest returns the PullRequest field.
+func (p *PullRequestReviewThreadEvent) GetPullRequest() *PullRequest {
+	if p == nil {
+		return nil
+	}
+	return p.PullRequest
+}
+
+// GetRepo returns the Repo field.
+func (p *PullRequestReviewThreadEvent) GetRepo() *Repository {
+	if p == nil {
+		return nil
+	}
+	return p.Repo
+}
+
+// GetSender returns the Sender field.
+func (p *PullRequestReviewThreadEvent) GetSender() *User {
+	if p == nil {
+		return nil
+	}
+	return p.Sender
+}
+
+// GetThread returns the Thread field.
+func (p *PullRequestReviewThreadEvent) GetThread() *PullRequestThread {
+	if p == nil {
+		return nil
+	}
+	return p.Thread
+}
+
 // GetAction returns the Action field if it's non-nil, zero value otherwise.
 func (p *PullRequestTargetEvent) GetAction() string {
 	if p == nil || p.Action == nil {
@@ -12932,6 +12982,22 @@ func (p *PullRequestTargetEvent) GetSender() *User {
 	return p.Sender
 }
 
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (p *PullRequestThread) GetID() int64 {
+	if p == nil || p.ID == nil {
+		return 0
+	}
+	return *p.ID
+}
+
+// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise.
+func (p *PullRequestThread) GetNodeID() string {
+	if p == nil || p.NodeID == nil {
+		return ""
+	}
+	return *p.NodeID
+}
+
 // GetMergablePulls returns the MergablePulls field if it's non-nil, zero value otherwise.
 func (p *PullStats) GetMergablePulls() int {
 	if p == nil || p.MergablePulls == nil {
diff --git a/github/messages.go b/github/messages.go
index c1e8161b..324d3b80 100644
--- a/github/messages.go
+++ b/github/messages.go
@@ -81,6 +81,7 @@ var (
 		"pull_request":                   "PullRequestEvent",
 		"pull_request_review":            "PullRequestReviewEvent",
 		"pull_request_review_comment":    "PullRequestReviewCommentEvent",
+		"pull_request_review_thread":     "PullRequestReviewThreadEvent",
 		"pull_request_target":            "PullRequestTargetEvent",
 		"push":                           "PushEvent",
 		"repository":                     "RepositoryEvent",
diff --git a/github/pulls_threads.go b/github/pulls_threads.go
new file mode 100644
index 00000000..23e924d8
--- /dev/null
+++ b/github/pulls_threads.go
@@ -0,0 +1,17 @@
+// Copyright 2022 The go-github AUTHORS. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package github
+
+// PullRequestThread represents a thread of comments on a pull request.
+type PullRequestThread struct {
+	ID       *int64                `json:"id,omitempty"`
+	NodeID   *string               `json:"node_id,omitempty"`
+	Comments []*PullRequestComment `json:"comments,omitempty"`
+}
+
+func (p PullRequestThread) String() string {
+	return Stringify(p)
+}


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 0045ad4b..c51af971 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -14,7 +14,7 @@ jobs:
   test:
     strategy:
       matrix:
-        go-version: [1.x, 1.16.x]
+        go-version: [1.x, 1.17.x]
         platform: [ubuntu-latest]
         include:
           # include windows, but only with the latest Go version, since there
diff --git a/github/event_types_test.go b/github/event_types_test.go
index 5f9dd258..5d82e3f4 100644
--- a/github/event_types_test.go
+++ b/github/event_types_test.go
@@ -13061,6 +13061,255 @@ func TestPullRequestReviewCommentEvent_Marshal(t *testing.T) {
 	testJSONMarshal(t, u, want)
 }
 
+func TestPullRequestReviewThreadEvent_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PullRequestReviewThreadEvent{}, "{}")
+
+	u := &PullRequestReviewThreadEvent{
+		Action:      String("a"),
+		PullRequest: &PullRequest{ID: Int64(1)},
+		Thread: &PullRequestThread{
+			Comments: []*PullRequestComment{{ID: Int64(1)}, {ID: Int64(2)}},
+		},
+		Repo: &Repository{
+			ID:   Int64(1),
+			URL:  String("s"),
+			Name: String("n"),
+		},
+		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": "a",
+		"pull_request": {
+			"id": 1
+		},
+		"thread": {
+			"comments": [
+				{
+					"id": 1
+				},
+				{
+					"id": 2
+				}
+			]
+		},
+		"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, u, want)
+}
+
 func TestPullRequestTargetEvent_Marshal(t *testing.T) {
 	testJSONMarshal(t, &PullRequestTargetEvent{}, "{}")
 
diff --git a/github/gen-stringify-test.go b/github/gen-stringify-test.go
index b78edad9..71783fe5 100644
--- a/github/gen-stringify-test.go
+++ b/github/gen-stringify-test.go
@@ -352,7 +352,19 @@ func (t *templateData) dump() error {
 	}
 
 	logf("Writing %v...", t.filename)
-	return ioutil.WriteFile(t.filename, clean, 0644)
+	if err := os.Chmod(t.filename, 0644); err != nil {
+		return fmt.Errorf("os.Chmod(%q, 0644): %v", t.filename, err)
+	}
+
+	if err := ioutil.WriteFile(t.filename, clean, 0444); err != nil {
+		return err
+	}
+
+	if err := os.Chmod(t.filename, 0444); err != nil {
+		return fmt.Errorf("os.Chmod(%q, 0444): %v", t.filename, err)
+	}
+
+	return nil
 }
 
 func newStructField(receiverType, fieldName, fieldType, zeroValue string, namedStruct bool) *structField {
@@ -379,6 +391,8 @@ const source = `// Copyright {{.Year}} The go-github AUTHORS. All rights reserve
 // license that can be found in the LICENSE file.
 
 // Code generated by gen-stringify-tests; DO NOT EDIT.
+// Instead, please run "go generate ./..." as described here:
+// https://github.com/google/go-github/blob/master/CONTRIBUTING.md#submitting-a-patch
 
 package {{ $package := .Package}}{{$package}}
 {{with .Imports}}
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index ab1039b8..5ac8d7b9 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -4,6 +4,8 @@
 // license that can be found in the LICENSE file.
 
 // Code generated by gen-accessors; DO NOT EDIT.
+// Instead, please run "go generate ./..." as described here:
+// https://github.com/google/go-github/blob/master/CONTRIBUTING.md#submitting-a-patch
 
 package github
 
@@ -14928,6 +14930,51 @@ func TestPullRequestReviewsEnforcementUpdate_GetRequireCodeOwnerReviews(tt *test
 	p.GetRequireCodeOwnerReviews()
 }
 
+func TestPullRequestReviewThreadEvent_GetAction(tt *testing.T) {
+	var zeroValue string
+	p := &PullRequestReviewThreadEvent{Action: &zeroValue}
+	p.GetAction()
+	p = &PullRequestReviewThreadEvent{}
+	p.GetAction()
+	p = nil
+	p.GetAction()
+}
+
+func TestPullRequestReviewThreadEvent_GetInstallation(tt *testing.T) {
+	p := &PullRequestReviewThreadEvent{}
+	p.GetInstallation()
+	p = nil
+	p.GetInstallation()
+}
+
+func TestPullRequestReviewThreadEvent_GetPullRequest(tt *testing.T) {
+	p := &PullRequestReviewThreadEvent{}
+	p.GetPullRequest()
+	p = nil
+	p.GetPullRequest()
+}
+
+func TestPullRequestReviewThreadEvent_GetRepo(tt *testing.T) {
+	p := &PullRequestReviewThreadEvent{}
+	p.GetRepo()
+	p = nil
+	p.GetRepo()
+}
+
+func TestPullRequestReviewThreadEvent_GetSender(tt *testing.T) {
+	p := &PullRequestReviewThreadEvent{}
+	p.GetSender()
+	p = nil
+	p.GetSender()
+}
+
+func TestPullRequestReviewThreadEvent_GetThread(tt *testing.T) {
+	p := &PullRequestReviewThreadEvent{}
+	p.GetThread()
+	p = nil
+	p.GetThread()
+}
+
 func TestPullRequestTargetEvent_GetAction(tt *testing.T) {
 	var zeroValue string
 	p := &PullRequestTargetEvent{Action: &zeroValue}
@@ -15038,6 +15085,26 @@ func TestPullRequestTargetEvent_GetSender(tt *testing.T) {
 	p.GetSender()
 }
 
+func TestPullRequestThread_GetID(tt *testing.T) {
+	var zeroValue int64
+	p := &PullRequestThread{ID: &zeroValue}
+	p.GetID()
+	p = &PullRequestThread{}
+	p.GetID()
+	p = nil
+	p.GetID()
+}
+
+func TestPullRequestThread_GetNodeID(tt *testing.T) {
+	var zeroValue string
+	p := &PullRequestThread{NodeID: &zeroValue}
+	p.GetNodeID()
+	p = &PullRequestThread{}
+	p.GetNodeID()
+	p = nil
+	p.GetNodeID()
+}
+
 func TestPullStats_GetMergablePulls(tt *testing.T) {
 	var zeroValue int
 	p := &PullStats{MergablePulls: &zeroValue}
diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go
index 4be5b620..f3934ec0 100644
--- a/github/github-stringify_test.go
+++ b/github/github-stringify_test.go
@@ -4,6 +4,8 @@
 // license that can be found in the LICENSE file.
 
 // Code generated by gen-stringify-tests; DO NOT EDIT.
+// Instead, please run "go generate ./..." as described here:
+// https://github.com/google/go-github/blob/master/CONTRIBUTING.md#submitting-a-patch
 
 package github
 
@@ -1320,6 +1322,17 @@ func TestPullRequestReviewRequest_String(t *testing.T) {
 	}
 }
 
+func TestPullRequestThread_String(t *testing.T) {
+	v := PullRequestThread{
+		ID:     Int64(0),
+		NodeID: String(""),
+	}
+	want := `github.PullRequestThread{ID:0, NodeID:""}`
+	if got := v.String(); got != want {
+		t.Errorf("PullRequestThread.String = %v, want %v", got, want)
+	}
+}
+
 func TestPullStats_String(t *testing.T) {
 	v := PullStats{
 		TotalPulls:      Int(0),
diff --git a/github/messages_test.go b/github/messages_test.go
index a0cf83f2..1d9b7f63 100644
--- a/github/messages_test.go
+++ b/github/messages_test.go
@@ -405,6 +405,10 @@ func TestParseWebHook(t *testing.T) {
 			payload:     &PullRequestReviewCommentEvent{},
 			messageType: "pull_request_review_comment",
 		},
+		{
+			payload:     &PullRequestReviewThreadEvent{},
+			messageType: "pull_request_review_thread",
+		},
 		{
 			payload:     &PullRequestTargetEvent{},
 			messageType: "pull_request_target",
diff --git a/github/pulls_threads_test.go b/github/pulls_threads_test.go
new file mode 100644
index 00000000..6df28404
--- /dev/null
+++ b/github/pulls_threads_test.go
@@ -0,0 +1,191 @@
+// Copyright 2022 The go-github AUTHORS. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package github
+
+import (
+	"testing"
+	"time"
+)
+
+func TestPullRequestThread_Marshal(t *testing.T) {
+	testJSONMarshal(t, &PullRequestThread{}, "{}")
+
+	createdAt := time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC)
+	updatedAt := time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC)
+	reactions := &Reactions{
+		TotalCount: Int(1),
+		PlusOne:    Int(1),
+		MinusOne:   Int(0),
+		Laugh:      Int(0),
+		Confused:   Int(0),
+		Heart:      Int(0),
+		Hooray:     Int(0),
+		Rocket:     Int(0),
+		Eyes:       Int(0),
+		URL:        String("u"),
+	}
+	user := &User{
+		Login:       String("ll"),
+		ID:          Int64(123),
+		AvatarURL:   String("a"),
+		GravatarID:  String("g"),
+		Name:        String("n"),
+		Company:     String("c"),
+		Blog:        String("b"),
+		Location:    String("l"),
+		Email:       String("e"),
+		Hireable:    Bool(true),
+		PublicRepos: Int(1),
+		Followers:   Int(1),
+		Following:   Int(1),
+		CreatedAt:   &Timestamp{referenceTime},
+		URL:         String("u"),
+	}
+	comment := &PullRequestComment{
+		ID:                  Int64(10),
+		InReplyTo:           Int64(8),
+		Body:                String("Test comment"),
+		Path:                String("file1.txt"),
+		DiffHunk:            String("@@ -16,33 +16,40 @@ fmt.Println()"),
+		PullRequestReviewID: Int64(42),
+		Position:            Int(1),
+		OriginalPosition:    Int(4),
+		StartLine:           Int(2),
+		Line:                Int(3),
+		OriginalLine:        Int(2),
+		OriginalStartLine:   Int(2),
+		Side:                String("RIGHT"),
+		StartSide:           String("LEFT"),
+		CommitID:            String("ab"),
+		OriginalCommitID:    String("9c"),
+		User:                user,
+		Reactions:           reactions,
+		CreatedAt:           &createdAt,
+		UpdatedAt:           &updatedAt,
+		URL:                 String("pullrequestcommentUrl"),
+		HTMLURL:             String("pullrequestcommentHTMLUrl"),
+		PullRequestURL:      String("pullrequestcommentPullRequestURL"),
+	}
+
+	u := &PullRequestThread{
+		ID:       Int64(1),
+		NodeID:   String("nid"),
+		Comments: []*PullRequestComment{comment, comment},
+	}
+
+	want := `{
+		"id": 1,
+		"node_id": "nid",
+		"comments": [
+			{
+				"id": 10,
+				"in_reply_to_id": 8,
+				"body": "Test comment",
+				"path": "file1.txt",
+				"diff_hunk": "@@ -16,33 +16,40 @@ fmt.Println()",
+				"pull_request_review_id": 42,
+				"position": 1,
+				"original_position": 4,
+				"start_line": 2,
+				"line": 3,
+				"original_line": 2,
+				"original_start_line": 2,
+				"side": "RIGHT",
+				"start_side": "LEFT",
+				"commit_id": "ab",
+				"original_commit_id": "9c",
+				"user": {
+					"login": "ll",
+					"id": 123,
+					"avatar_url": "a",
+					"gravatar_id": "g",
+					"name": "n",
+					"company": "c",
+					"blog": "b",
+					"location": "l",
+					"email": "e",
+					"hireable": true,
+					"public_repos": 1,
+					"followers": 1,
+					"following": 1,
+					"created_at": ` + referenceTimeStr + `,
+					"url": "u"
+				},
+				"reactions": {
+					"total_count": 1,
+					"+1": 1,
+					"-1": 0,
+					"laugh": 0,
+					"confused": 0,
+					"heart": 0,
+					"hooray": 0,
+					"rocket": 0,
+					"eyes": 0,
+					"url": "u"
+				},
+				"created_at": "2002-02-10T15:30:00Z",
+				"updated_at": "2002-02-10T15:30:00Z",
+				"url": "pullrequestcommentUrl",
+				"html_url": "pullrequestcommentHTMLUrl",
+				"pull_request_url": "pullrequestcommentPullRequestURL"
+			},
+			{
+				"id": 10,
+				"in_reply_to_id": 8,
+				"body": "Test comment",
+				"path": "file1.txt",
+				"diff_hunk": "@@ -16,33 +16,40 @@ fmt.Println()",
+				"pull_request_review_id": 42,
+				"position": 1,
+				"original_position": 4,
+				"start_line": 2,
+				"line": 3,
+				"original_line": 2,
+				"original_start_line": 2,
+				"side": "RIGHT",
+				"start_side": "LEFT",
+				"commit_id": "ab",
+				"original_commit_id": "9c",
+				"user": {
+					"login": "ll",
+					"id": 123,
+					"avatar_url": "a",
+					"gravatar_id": "g",
+					"name": "n",
+					"company": "c",
+					"blog": "b",
+					"location": "l",
+					"email": "e",
+					"hireable": true,
+					"public_repos": 1,
+					"followers": 1,
+					"following": 1,
+					"created_at": ` + referenceTimeStr + `,
+					"url": "u"
+				},
+				"reactions": {
+					"total_count": 1,
+					"+1": 1,
+					"-1": 0,
+					"laugh": 0,
+					"confused": 0,
+					"heart": 0,
+					"hooray": 0,
+					"rocket": 0,
+					"eyes": 0,
+					"url": "u"
+				},
+				"created_at": "2002-02-10T15:30:00Z",
+				"updated_at": "2002-02-10T15:30:00Z",
+				"url": "pullrequestcommentUrl",
+				"html_url": "pullrequestcommentHTMLUrl",
+				"pull_request_url": "pullrequestcommentPullRequestURL"
+			}
+		]
+	}`
+
+	testJSONMarshal(t, u, want)
+}
diff --git a/github/repos_hooks_deliveries_test.go b/github/repos_hooks_deliveries_test.go
index 1a276859..eea17386 100644
--- a/github/repos_hooks_deliveries_test.go
+++ b/github/repos_hooks_deliveries_test.go
@@ -178,6 +178,7 @@ var hookDeliveryPayloadTypeToStruct = map[string]interface{}{
 	"pull_request":                   &PullRequestEvent{},
 	"pull_request_review":            &PullRequestReviewEvent{},
 	"pull_request_review_comment":    &PullRequestReviewCommentEvent{},
+	"pull_request_review_thread":     &PullRequestReviewThreadEvent{},
 	"pull_request_target":            &PullRequestTargetEvent{},
 	"push":                           &PushEvent{},
 	"release":                        &ReleaseEvent{},