Repository: google/go-github
Pull request #1955 (2021-07-08 20:02:02)

Expose event message headers, introduce a new way to read the body from an `io.Reader`
This pull request introduces the changes described in #1944.

- Expose the constants `sha1SignatureHeader`, `sha256SignatureHeader`, `eventTypeHeader` and `deliveryIDHeader`
- Introduce a new public function called `ValidatePayloadFromBody` to read the body directly from an `io.Reader` rather than a `http.Request`.

The changes described enables the possibility to use `go-github` even with http libraries like `fiber` or `fasthttp`.

Example A:

```go
app.Get(func(ctx *fiber.Ctx) error {
	signature := ctx.Get(github.SHA256SignatureHeader)
	if signature == "" {
		signature = ctx.Get(github.SHA1SignatureHeader)
	}
	
	payload := ctx.Body()
	err := github.ValidateSignature(signature, payload, mySecretToken)
	// ...

	event, err := github.ParseWebHook(ctx.Get(github.EventTypeHeader), payload)
	// ...
})
```

Example B:

```go
app.Get(func(ctx *fiber.Ctx) error {
	signature := ctx.Get(github.SHA256SignatureHeader)
	if signature == "" {
		signature = ctx.Get(github.SHA1SignatureHeader)
	}
	
	payload, err := github.ValidatePayloadFromBody(ctx.Get("Content-Type"), ctx.Context().RequestBodyStream(), signature, mySecretToken)
	// ...

	event, err := github.ParseWebHook(ctx.Get(github.EventTypeHeader), payload)
	// ...
})
```



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

:memo: **Please visit <https://cla.developers.google.com/> to sign.**

Once you've signed (or fixed any issues), please reply here with `@googlebot I signed it!` and we'll verify it.

----

#### What to do if you already signed the CLA

##### Individual signers

*   It's possible we don't have your GitHub username or you're using a different email address on your commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).

##### Corporate signers

*   Your company has a Point of Contact who decides which employees are authorized to participate. Ask your POC to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the Google project maintainer to [go/cla#troubleshoot](http://go/cla#troubleshoot) ([Public version](https://opensource.google/docs/cla/#troubleshoot)).
*   The email used to register you as an authorized contributor must be the email used for the Git commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
*   The email used to register you as an authorized contributor must also be [attached to your GitHub account](https://github.com/settings/emails).
		

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogle%2Fgo-github%2Fpull%2F1955) for more info**.

<!-- need_sender_cla -->
m19c: @googlebot I signed it!
codecov[bot]: # [Codecov](https://codecov.io/gh/google/go-github/pull/1955?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#1955](https://codecov.io/gh/google/go-github/pull/1955?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (bd39e80) into [master](https://codecov.io/gh/google/go-github/commit/c26abbd349558ee6d1a674f1113ecca5b5e9f6d5?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (c26abbd) will **increase** coverage by `0.18%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/google/go-github/pull/1955/graphs/tree.svg?width=650&height=150&src=pr&token=EgYjGjZlI0&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)](https://codecov.io/gh/google/go-github/pull/1955?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)

```diff
@@            Coverage Diff             @@
##           master    #1955      +/-   ##
==========================================
+ Coverage   97.65%   97.83%   +0.18%     
==========================================
  Files         105      105              
  Lines        6786     6804      +18     
==========================================
+ Hits         6627     6657      +30     
+ Misses         86       80       -6     
+ Partials       73       67       -6     
```


| [Impacted Files](https://codecov.io/gh/google/go-github/pull/1955?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Coverage Δ | |
|---|---|---|
| [github/messages.go](https://codecov.io/gh/google/go-github/pull/1955/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL21lc3NhZ2VzLmdv) | `96.92% <100.00%> (+0.04%)` | :arrow_up: |
| [github/github.go](https://codecov.io/gh/google/go-github/pull/1955/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL2dpdGh1Yi5nbw==) | `97.01% <0.00%> (ø)` | |
| [github/activity.go](https://codecov.io/gh/google/go-github/pull/1955/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL2FjdGl2aXR5Lmdv) | `100.00% <0.00%> (ø)` | |
| [github/event\_types.go](https://codecov.io/gh/google/go-github/pull/1955/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL2V2ZW50X3R5cGVzLmdv) | `100.00% <0.00%> (ø)` | |
| [github/repos.go](https://codecov.io/gh/google/go-github/pull/1955/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL3JlcG9zLmdv) | `98.75% <0.00%> (+0.04%)` | :arrow_up: |
| [github/repos\_contents.go](https://codecov.io/gh/google/go-github/pull/1955/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL3JlcG9zX2NvbnRlbnRzLmdv) | `89.68% <0.00%> (+3.17%)` | :arrow_up: |
| [github/actions\_artifacts.go](https://codecov.io/gh/google/go-github/pull/1955/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL2FjdGlvbnNfYXJ0aWZhY3RzLmdv) | `100.00% <0.00%> (+6.45%)` | :arrow_up: |
| [github/actions\_workflow\_jobs.go](https://codecov.io/gh/google/go-github/pull/1955/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-Z2l0aHViL2FjdGlvbnNfd29ya2Zsb3dfam9icy5nbw==) | `100.00% <0.00%> (+8.88%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/google/go-github/pull/1955?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/go-github/pull/1955?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Last update [c26abbd...bd39e80](https://codecov.io/gh/google/go-github/pull/1955?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).

gmlewis: @m19c - do you want to resolve the merge conflicts, or would you like me to do that?
gmlewis: Thank you, @m19c !  Could you please run `gofmt` one more time and push the changes to make the tests pass?
m19c: Done. Intent should be fixed now.
gmlewis: @m19c - bonus points if you can increase the code coverage by writing a test for your new method (while we are waiting for a second LGTM).  :smile:
m19c: Done 👍🏽 

Patch:
diff --git a/README.md b/README.md
index 6c72cdb3..6effaf7c 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # go-github #
 
 [![go-github release (latest SemVer)](https://img.shields.io/github/v/release/google/go-github?sort=semver)](https://github.com/google/go-github/releases)
-[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/google/go-github/v36/github)
+[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/google/go-github/v37/github)
 [![Test Status](https://github.com/google/go-github/workflows/tests/badge.svg)](https://github.com/google/go-github/actions?query=workflow%3Atests)
 [![Test Coverage](https://codecov.io/gh/google/go-github/branch/master/graph/badge.svg)](https://codecov.io/gh/google/go-github)
 [![Discuss at go-github@googlegroups.com](https://img.shields.io/badge/discuss-go--github%40googlegroups.com-blue.svg)](https://groups.google.com/group/go-github)
@@ -24,7 +24,7 @@ If you're interested in using the [GraphQL API v4][], the recommended library is
 go-github is compatible with modern Go releases in module mode, with Go installed:
 
 ```bash
-go get github.com/google/go-github/v36
+go get github.com/google/go-github/v37
 ```
 
 will resolve and add the package to the current development module, along with its dependencies.
@@ -32,7 +32,7 @@ will resolve and add the package to the current development module, along with i
 Alternatively the same can be achieved if you use import in a package:
 
 ```go
-import "github.com/google/go-github/v36/github"
+import "github.com/google/go-github/v37/github"
 ```
 
 and run `go get` without parameters.
@@ -40,13 +40,13 @@ and run `go get` without parameters.
 Finally, to use the top-of-trunk version of this repo, use the following command:
 
 ```bash
-go get github.com/google/go-github/v36@master
+go get github.com/google/go-github/v37@master
 ```
 
 ## Usage ##
 
 ```go
-import "github.com/google/go-github/v36/github"	// with go modules enabled (GO111MODULE=on or outside GOPATH)
+import "github.com/google/go-github/v37/github"	// with go modules enabled (GO111MODULE=on or outside GOPATH)
 import "github.com/google/go-github/github" // with go modules disabled
 ```
 
@@ -246,7 +246,7 @@ For complete usage of go-github, see the full [package docs][].
 [oauth2]: https://github.com/golang/oauth2
 [oauth2 docs]: https://godoc.org/golang.org/x/oauth2
 [personal API token]: https://github.com/blog/1509-personal-api-tokens
-[package docs]: https://pkg.go.dev/github.com/google/go-github/v36/github
+[package docs]: https://pkg.go.dev/github.com/google/go-github/v37/github
 [GraphQL API v4]: https://developer.github.com/v4/
 [shurcooL/githubv4]: https://github.com/shurcooL/githubv4
 
diff --git a/example/appengine/app.go b/example/appengine/app.go
index 893dca9d..75c90528 100644
--- a/example/appengine/app.go
+++ b/example/appengine/app.go
@@ -12,7 +12,7 @@ import (
 	"net/http"
 	"os"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 	"golang.org/x/oauth2"
 	"google.golang.org/appengine"
 	"google.golang.org/appengine/log"
diff --git a/example/basicauth/main.go b/example/basicauth/main.go
index d172d9b0..09ab1180 100644
--- a/example/basicauth/main.go
+++ b/example/basicauth/main.go
@@ -22,7 +22,7 @@ import (
 	"strings"
 	"syscall"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 	"golang.org/x/crypto/ssh/terminal"
 )
 
diff --git a/example/commitpr/main.go b/example/commitpr/main.go
index 856d1cb5..33572f20 100644
--- a/example/commitpr/main.go
+++ b/example/commitpr/main.go
@@ -31,7 +31,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 	"golang.org/x/oauth2"
 )
 
diff --git a/example/migrations/main.go b/example/migrations/main.go
index 8e4e8a08..42218b7a 100644
--- a/example/migrations/main.go
+++ b/example/migrations/main.go
@@ -12,7 +12,7 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 	"golang.org/x/oauth2"
 )
 
diff --git a/example/newrepo/main.go b/example/newrepo/main.go
index cc1525a0..6190467f 100644
--- a/example/newrepo/main.go
+++ b/example/newrepo/main.go
@@ -16,7 +16,7 @@ import (
 	"log"
 	"os"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 	"golang.org/x/oauth2"
 )
 
diff --git a/example/newreposecret/go.mod b/example/newreposecret/go.mod
index af3a4ea8..5f3e7027 100644
--- a/example/newreposecret/go.mod
+++ b/example/newreposecret/go.mod
@@ -4,7 +4,7 @@ go 1.15
 
 require (
 	github.com/GoKillers/libsodium-go v0.0.0-20171022220152-dd733721c3cb
-	github.com/google/go-github/v36 v36.0.0
+	github.com/google/go-github/v37 v37.0.0
 	golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
 	gopkg.in/yaml.v2 v2.3.0 // indirect
 )
diff --git a/example/newreposecret/main.go b/example/newreposecret/main.go
index 1bf50024..08d5fc92 100644
--- a/example/newreposecret/main.go
+++ b/example/newreposecret/main.go
@@ -34,7 +34,7 @@ import (
 	"os"
 
 	sodium "github.com/GoKillers/libsodium-go/cryptobox"
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 	"golang.org/x/oauth2"
 )
 
diff --git a/example/simple/main.go b/example/simple/main.go
index 3b559ade..2b3d8732 100644
--- a/example/simple/main.go
+++ b/example/simple/main.go
@@ -12,7 +12,7 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 )
 
 // Fetch all the public organizations' membership of a user.
diff --git a/example/tokenauth/main.go b/example/tokenauth/main.go
index 773f20c8..7f172965 100644
--- a/example/tokenauth/main.go
+++ b/example/tokenauth/main.go
@@ -14,7 +14,7 @@ import (
 	"log"
 	"syscall"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 	"golang.org/x/crypto/ssh/terminal"
 	"golang.org/x/oauth2"
 )
diff --git a/example/topics/main.go b/example/topics/main.go
index 982cdcad..0c0787f4 100644
--- a/example/topics/main.go
+++ b/example/topics/main.go
@@ -12,7 +12,7 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 )
 
 // Fetch and lists all the public topics associated with the specified GitHub topic
diff --git a/github/checks.go b/github/checks.go
index cb8b847f..07afb7c4 100644
--- a/github/checks.go
+++ b/github/checks.go
@@ -78,6 +78,8 @@ type CheckSuite struct {
 	AfterSHA     *string        `json:"after,omitempty"`
 	Status       *string        `json:"status,omitempty"`
 	Conclusion   *string        `json:"conclusion,omitempty"`
+	CreatedAt    *Timestamp     `json:"created_at,omitempty"`
+	UpdatedAt    *Timestamp     `json:"updated_at,omitempty"`
 	App          *App           `json:"app,omitempty"`
 	Repository   *Repository    `json:"repository,omitempty"`
 	PullRequests []*PullRequest `json:"pull_requests,omitempty"`
diff --git a/github/doc.go b/github/doc.go
index 4daa64d9..bd2e3ea0 100644
--- a/github/doc.go
+++ b/github/doc.go
@@ -8,7 +8,7 @@ Package github provides a client for using the GitHub API.
 
 Usage:
 
-	import "github.com/google/go-github/v36/github"	// with go modules enabled (GO111MODULE=on or outside GOPATH)
+	import "github.com/google/go-github/v37/github"	// with go modules enabled (GO111MODULE=on or outside GOPATH)
 	import "github.com/google/go-github/github"     // with go modules disabled
 
 Construct a new GitHub client, then use the various services on the client to
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 46efcc9c..fb3e2903 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -1612,6 +1612,14 @@ func (c *CheckSuite) GetConclusion() string {
 	return *c.Conclusion
 }
 
+// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
+func (c *CheckSuite) GetCreatedAt() Timestamp {
+	if c == nil || c.CreatedAt == nil {
+		return Timestamp{}
+	}
+	return *c.CreatedAt
+}
+
 // GetHeadBranch returns the HeadBranch field if it's non-nil, zero value otherwise.
 func (c *CheckSuite) GetHeadBranch() string {
 	if c == nil || c.HeadBranch == nil {
@@ -1668,6 +1676,14 @@ func (c *CheckSuite) GetStatus() string {
 	return *c.Status
 }
 
+// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise.
+func (c *CheckSuite) GetUpdatedAt() Timestamp {
+	if c == nil || c.UpdatedAt == nil {
+		return Timestamp{}
+	}
+	return *c.UpdatedAt
+}
+
 // GetURL returns the URL field if it's non-nil, zero value otherwise.
 func (c *CheckSuite) GetURL() string {
 	if c == nil || c.URL == nil {
@@ -4836,6 +4852,142 @@ func (h *HookConfig) GetURL() string {
 	return *h.URL
 }
 
+// GetAction returns the Action field if it's non-nil, zero value otherwise.
+func (h *HookDelivery) GetAction() string {
+	if h == nil || h.Action == nil {
+		return ""
+	}
+	return *h.Action
+}
+
+// GetDeliveredAt returns the DeliveredAt field if it's non-nil, zero value otherwise.
+func (h *HookDelivery) GetDeliveredAt() Timestamp {
+	if h == nil || h.DeliveredAt == nil {
+		return Timestamp{}
+	}
+	return *h.DeliveredAt
+}
+
+// GetDuration returns the Duration field.
+func (h *HookDelivery) GetDuration() *float64 {
+	if h == nil {
+		return nil
+	}
+	return h.Duration
+}
+
+// GetEvent returns the Event field if it's non-nil, zero value otherwise.
+func (h *HookDelivery) GetEvent() string {
+	if h == nil || h.Event == nil {
+		return ""
+	}
+	return *h.Event
+}
+
+// GetGUID returns the GUID field if it's non-nil, zero value otherwise.
+func (h *HookDelivery) GetGUID() string {
+	if h == nil || h.GUID == nil {
+		return ""
+	}
+	return *h.GUID
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (h *HookDelivery) GetID() int64 {
+	if h == nil || h.ID == nil {
+		return 0
+	}
+	return *h.ID
+}
+
+// GetInstallationID returns the InstallationID field if it's non-nil, zero value otherwise.
+func (h *HookDelivery) GetInstallationID() string {
+	if h == nil || h.InstallationID == nil {
+		return ""
+	}
+	return *h.InstallationID
+}
+
+// GetRedelivery returns the Redelivery field if it's non-nil, zero value otherwise.
+func (h *HookDelivery) GetRedelivery() bool {
+	if h == nil || h.Redelivery == nil {
+		return false
+	}
+	return *h.Redelivery
+}
+
+// GetRepositoryID returns the RepositoryID field if it's non-nil, zero value otherwise.
+func (h *HookDelivery) GetRepositoryID() int64 {
+	if h == nil || h.RepositoryID == nil {
+		return 0
+	}
+	return *h.RepositoryID
+}
+
+// GetRequest returns the Request field.
+func (h *HookDelivery) GetRequest() *HookRequest {
+	if h == nil {
+		return nil
+	}
+	return h.Request
+}
+
+// GetResponse returns the Response field.
+func (h *HookDelivery) GetResponse() *HookResponse {
+	if h == nil {
+		return nil
+	}
+	return h.Response
+}
+
+// GetStatus returns the Status field if it's non-nil, zero value otherwise.
+func (h *HookDelivery) GetStatus() string {
+	if h == nil || h.Status == nil {
+		return ""
+	}
+	return *h.Status
+}
+
+// GetStatusCode returns the StatusCode field if it's non-nil, zero value otherwise.
+func (h *HookDelivery) GetStatusCode() int {
+	if h == nil || h.StatusCode == nil {
+		return 0
+	}
+	return *h.StatusCode
+}
+
+// GetHeaders returns the Headers map if it's non-nil, an empty map otherwise.
+func (h *HookRequest) GetHeaders() map[string]string {
+	if h == nil || h.Headers == nil {
+		return map[string]string{}
+	}
+	return h.Headers
+}
+
+// GetRawPayload returns the RawPayload field if it's non-nil, zero value otherwise.
+func (h *HookRequest) GetRawPayload() json.RawMessage {
+	if h == nil || h.RawPayload == nil {
+		return json.RawMessage{}
+	}
+	return *h.RawPayload
+}
+
+// GetHeaders returns the Headers map if it's non-nil, an empty map otherwise.
+func (h *HookResponse) GetHeaders() map[string]string {
+	if h == nil || h.Headers == nil {
+		return map[string]string{}
+	}
+	return h.Headers
+}
+
+// GetRawPayload returns the RawPayload field if it's non-nil, zero value otherwise.
+func (h *HookResponse) GetRawPayload() json.RawMessage {
+	if h == nil || h.RawPayload == nil {
+		return json.RawMessage{}
+	}
+	return *h.RawPayload
+}
+
 // GetActiveHooks returns the ActiveHooks field if it's non-nil, zero value otherwise.
 func (h *HookStats) GetActiveHooks() int {
 	if h == nil || h.ActiveHooks == nil {
@@ -10052,6 +10204,14 @@ func (p *ProtectionRequest) GetAllowForcePushes() bool {
 	return *p.AllowForcePushes
 }
 
+// GetRequiredConversationResolution returns the RequiredConversationResolution field if it's non-nil, zero value otherwise.
+func (p *ProtectionRequest) GetRequiredConversationResolution() bool {
+	if p == nil || p.RequiredConversationResolution == nil {
+		return false
+	}
+	return *p.RequiredConversationResolution
+}
+
 // GetRequiredPullRequestReviews returns the RequiredPullRequestReviews field.
 func (p *ProtectionRequest) GetRequiredPullRequestReviews() *PullRequestReviewsEnforcementRequest {
 	if p == nil {
diff --git a/github/github.go b/github/github.go
index 264e27f4..ae0ee08c 100644
--- a/github/github.go
+++ b/github/github.go
@@ -212,6 +212,9 @@ type ListCursorOptions struct {
 
 	// A cursor, as given in the Link header. If specified, the query only searches for events before this cursor.
 	Before string `url:"before,omitempty"`
+
+	// A cursor, as given in the Link header. If specified, the query continues the search using this cursor.
+	Cursor string `url:"cursor,omitempty"`
 }
 
 // UploadOptions specifies the parameters to methods that support uploads.
@@ -445,6 +448,11 @@ type Response struct {
 	// calling the endpoint again.
 	NextPageToken string
 
+	// For APIs that support cursor pagination, such as RepositoryService.ListRepositoryHookDeliveries,
+	// the following field will be populated to point to the next page.
+	// Set ListCursorOptions.Cursor to this value when calling the endpoint again.
+	Cursor string
+
 	// Explicitly specify the Rate type so Rate's String() receiver doesn't
 	// propagate to Response.
 	Rate Rate
@@ -481,7 +489,21 @@ func (r *Response) populatePageValues() {
 			if err != nil {
 				continue
 			}
-			page := url.Query().Get("page")
+
+			q := url.Query()
+
+			if cursor := q.Get("cursor"); cursor != "" {
+				for _, segment := range segments[1:] {
+					switch strings.TrimSpace(segment) {
+					case `rel="next"`:
+						r.Cursor = cursor
+					}
+				}
+
+				continue
+			}
+
+			page := q.Get("page")
 			if page == "" {
 				continue
 			}
@@ -499,7 +521,6 @@ func (r *Response) populatePageValues() {
 				case `rel="last"`:
 					r.LastPage, _ = strconv.Atoi(page)
 				}
-
 			}
 		}
 	}
diff --git a/github/messages.go b/github/messages.go
index 06ea9e4c..caa596a2 100644
--- a/github/messages.go
+++ b/github/messages.go
@@ -18,6 +18,7 @@ import (
 	"errors"
 	"fmt"
 	"hash"
+	"io"
 	"io/ioutil"
 	"mime"
 	"net/http"
@@ -31,14 +32,14 @@ const (
 	// sha256Prefix and sha512Prefix are provided for future compatibility.
 	sha256Prefix = "sha256"
 	sha512Prefix = "sha512"
-	// sha1SignatureHeader is the GitHub header key used to pass the HMAC-SHA1 hexdigest.
-	sha1SignatureHeader = "X-Hub-Signature"
-	// sha256SignatureHeader is the GitHub header key used to pass the HMAC-SHA256 hexdigest.
-	sha256SignatureHeader = "X-Hub-Signature-256"
-	// eventTypeHeader is the GitHub header key used to pass the event type.
-	eventTypeHeader = "X-Github-Event"
-	// deliveryIDHeader is the GitHub header key used to pass the unique ID for the webhook event.
-	deliveryIDHeader = "X-Github-Delivery"
+	// SHA1SignatureHeader is the GitHub header key used to pass the HMAC-SHA1 hexdigest.
+	SHA1SignatureHeader = "X-Hub-Signature"
+	// SHA256SignatureHeader is the GitHub header key used to pass the HMAC-SHA256 hexdigest.
+	SHA256SignatureHeader = "X-Hub-Signature-256"
+	// EventTypeHeader is the GitHub header key used to pass the event type.
+	EventTypeHeader = "X-Github-Event"
+	// DeliveryIDHeader is the GitHub header key used to pass the unique ID for the webhook event.
+	DeliveryIDHeader = "X-Github-Delivery"
 )
 
 var (
@@ -139,7 +140,7 @@ func messageMAC(signature string) ([]byte, func() hash.Hash, error) {
 	return buf, hashFunc, nil
 }
 
-// ValidatePayload validates an incoming GitHub Webhook event request
+// ValidatePayload validates an incoming GitHub Webhook event request body
 // and returns the (JSON) payload.
 // The Content-Type header of the payload can be "application/json" or "application/x-www-form-urlencoded".
 // If the Content-Type is neither then an error is returned.
@@ -150,25 +151,19 @@ func messageMAC(signature string) ([]byte, func() hash.Hash, error) {
 // Example usage:
 //
 //     func (s *GitHubEventMonitor) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-//       payload, err := github.ValidatePayload(r, s.webhookSecretKey)
+//       // read signature from request
+//       signature := ""
+//       payload, err := github.ValidatePayloadFromBody(r.Header.Get("Content-Type"), r.Body, signature, s.webhookSecretKey)
 //       if err != nil { ... }
 //       // Process payload...
 //     }
-//
-func ValidatePayload(r *http.Request, secretToken []byte) (payload []byte, err error) {
+func ValidatePayloadFromBody(contentType string, readable io.Reader, signature string, secretToken []byte) (payload []byte, err error) {
 	var body []byte // Raw body that GitHub uses to calculate the signature.
 
-	ct := r.Header.Get("Content-Type")
-
-	mediatype, _, err := mime.ParseMediaType(ct)
-	if err != nil {
-		mediatype = ""
-	}
-
-	switch mediatype {
+	switch contentType {
 	case "application/json":
 		var err error
-		if body, err = ioutil.ReadAll(r.Body); err != nil {
+		if body, err = ioutil.ReadAll(readable); err != nil {
 			return nil, err
 		}
 
@@ -182,7 +177,7 @@ func ValidatePayload(r *http.Request, secretToken []byte) (payload []byte, err e
 		const payloadFormParam = "payload"
 
 		var err error
-		if body, err = ioutil.ReadAll(r.Body); err != nil {
+		if body, err = ioutil.ReadAll(readable); err != nil {
 			return nil, err
 		}
 
@@ -195,17 +190,13 @@ func ValidatePayload(r *http.Request, secretToken []byte) (payload []byte, err e
 		payload = []byte(form.Get(payloadFormParam))
 
 	default:
-		return nil, fmt.Errorf("Webhook request has unsupported Content-Type %q", ct)
+		return nil, fmt.Errorf("Webhook request has unsupported Content-Type %q", contentType)
 	}
 
 	// Only validate the signature if a secret token exists. This is intended for
 	// local development only and all webhooks should ideally set up a secret token.
 	if len(secretToken) > 0 {
-		sig := r.Header.Get(sha256SignatureHeader)
-		if sig == "" {
-			sig = r.Header.Get(sha1SignatureHeader)
-		}
-		if err := ValidateSignature(sig, body, secretToken); err != nil {
+		if err := ValidateSignature(signature, body, secretToken); err != nil {
 			return nil, err
 		}
 	}
@@ -213,6 +204,36 @@ func ValidatePayload(r *http.Request, secretToken []byte) (payload []byte, err e
 	return payload, nil
 }
 
+// ValidatePayload validates an incoming GitHub Webhook event request
+// and returns the (JSON) payload.
+// The Content-Type header of the payload can be "application/json" or "application/x-www-form-urlencoded".
+// If the Content-Type is neither then an error is returned.
+// secretToken is the GitHub Webhook secret token.
+// If your webhook does not contain a secret token, you can pass nil or an empty slice.
+// This is intended for local development purposes only and all webhooks should ideally set up a secret token.
+//
+// Example usage:
+//
+//     func (s *GitHubEventMonitor) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+//       payload, err := github.ValidatePayload(r, s.webhookSecretKey)
+//       if err != nil { ... }
+//       // Process payload...
+//     }
+//
+func ValidatePayload(r *http.Request, secretToken []byte) (payload []byte, err error) {
+	signature := r.Header.Get(SHA256SignatureHeader)
+	if signature == "" {
+		signature = r.Header.Get(SHA1SignatureHeader)
+	}
+
+	contentType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
+	if err != nil {
+		return nil, err
+	}
+
+	return ValidatePayloadFromBody(contentType, r.Body, signature, secretToken)
+}
+
 // ValidateSignature validates the signature for the given payload.
 // signature is the GitHub hash signature delivered in the X-Hub-Signature header.
 // payload is the JSON payload sent by GitHub Webhooks.
@@ -234,14 +255,14 @@ func ValidateSignature(signature string, payload, secretToken []byte) error {
 //
 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/hooks/#webhook-headers
 func WebHookType(r *http.Request) string {
-	return r.Header.Get(eventTypeHeader)
+	return r.Header.Get(EventTypeHeader)
 }
 
 // DeliveryID returns the unique delivery ID of webhook request r.
 //
 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/hooks/#webhook-headers
 func DeliveryID(r *http.Request) string {
-	return r.Header.Get(deliveryIDHeader)
+	return r.Header.Get(DeliveryIDHeader)
 }
 
 // ParseWebHook parses the event payload. For recognized event types, a
diff --git a/github/orgs_hooks_deliveries.go b/github/orgs_hooks_deliveries.go
new file mode 100644
index 00000000..6ab2d7aa
--- /dev/null
+++ b/github/orgs_hooks_deliveries.go
@@ -0,0 +1,54 @@
+// Copyright 2021 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 (
+	"context"
+	"fmt"
+)
+
+// ListHookDeliveries lists webhook deliveries for a webhook configured in an organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/orgs#list-deliveries-for-an-organization-webhook
+func (s *OrganizationsService) ListHookDeliveries(ctx context.Context, org string, id int64, opts *ListCursorOptions) ([]*HookDelivery, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/hooks/%v/deliveries", org, id)
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	deliveries := []*HookDelivery{}
+	resp, err := s.client.Do(ctx, req, &deliveries)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return deliveries, resp, nil
+}
+
+// GetHookDelivery returns a delivery for a webhook configured in an organization.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/orgs#get-a-webhook-delivery-for-an-organization-webhook
+func (s *OrganizationsService) GetHookDelivery(ctx context.Context, owner string, hookID, deliveryID int64) (*HookDelivery, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/hooks/%v/deliveries/%v", owner, hookID, deliveryID)
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	h := new(HookDelivery)
+	resp, err := s.client.Do(ctx, req, h)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return h, resp, nil
+}
diff --git a/github/repos.go b/github/repos.go
index d336e8bf..eaadbb43 100644
--- a/github/repos.go
+++ b/github/repos.go
@@ -776,6 +776,9 @@ type ProtectionRequest struct {
 	AllowForcePushes *bool `json:"allow_force_pushes,omitempty"`
 	// Allows deletion of the protected branch by anyone with write access to the repository.
 	AllowDeletions *bool `json:"allow_deletions,omitempty"`
+	// RequiredConversationResolution, if set to true, requires all comments
+	// on the pull request to be resolved before it can be merged to a protected branch.
+	RequiredConversationResolution *bool `json:"required_conversation_resolution,omitempty"`
 }
 
 // RequiredStatusChecks represents the protection status of a individual branch.
diff --git a/github/repos_hooks_deliveries.go b/github/repos_hooks_deliveries.go
new file mode 100644
index 00000000..12267446
--- /dev/null
+++ b/github/repos_hooks_deliveries.go
@@ -0,0 +1,117 @@
+// Copyright 2021 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 (
+	"context"
+	"encoding/json"
+	"fmt"
+)
+
+// HookDelivery represents the data that is received from GitHub's Webhook Delivery API
+//
+// GitHub API docs:
+// - https://docs.github.com/en/rest/reference/repos#list-deliveries-for-a-repository-webhook
+// - https://docs.github.com/en/rest/reference/repos#get-a-delivery-for-a-repository-webhook
+type HookDelivery struct {
+	ID             *int64     `json:"id"`
+	GUID           *string    `json:"guid"`
+	DeliveredAt    *Timestamp `json:"delivered_at"`
+	Redelivery     *bool      `json:"redelivery"`
+	Duration       *float64   `json:"duration"`
+	Status         *string    `json:"status"`
+	StatusCode     *int       `json:"status_code"`
+	Event          *string    `json:"event"`
+	Action         *string    `json:"action"`
+	InstallationID *string    `json:"installation_id"`
+	RepositoryID   *int64     `json:"repository_id"`
+
+	// Request is populated by GetHookDelivery.
+	Request *HookRequest `json:"request,omitempty"`
+	// Response is populated by GetHookDelivery.
+	Response *HookResponse `json:"response,omitempty"`
+}
+
+func (d HookDelivery) String() string {
+	return Stringify(d)
+}
+
+// HookRequest is a part of HookDelivery that contains
+// the HTTP headers and the JSON payload of the webhook request.
+type HookRequest struct {
+	Headers    map[string]string `json:"headers,omitempty"`
+	RawPayload *json.RawMessage  `json:"payload,omitempty"`
+}
+
+func (r HookRequest) String() string {
+	return Stringify(r)
+}
+
+// HookResponse is a part of HookDelivery that contains
+// the HTTP headers and the response body served by the webhook endpoint.
+type HookResponse struct {
+	Headers    map[string]string `json:"headers,omitempty"`
+	RawPayload *json.RawMessage  `json:"payload,omitempty"`
+}
+
+func (r HookResponse) String() string {
+	return Stringify(r)
+}
+
+// ListHookDeliveries lists webhook deliveries for a webhook configured in a repository.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/repos#list-deliveries-for-a-repository-webhook
+func (s *RepositoriesService) ListHookDeliveries(ctx context.Context, owner, repo string, id int64, opts *ListCursorOptions) ([]*HookDelivery, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/hooks/%v/deliveries", owner, repo, id)
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	deliveries := []*HookDelivery{}
+	resp, err := s.client.Do(ctx, req, &deliveries)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return deliveries, resp, nil
+}
+
+// GetHookDelivery returns a delivery for a webhook configured in a repository.
+//
+// GitHub API docs: https://docs.github.com/en/rest/reference/repos#get-a-delivery-for-a-repository-webhook
+func (s *RepositoriesService) GetHookDelivery(ctx context.Context, owner, repo string, hookID, deliveryID int64) (*HookDelivery, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/hooks/%v/deliveries/%v", owner, repo, hookID, deliveryID)
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	h := new(HookDelivery)
+	resp, err := s.client.Do(ctx, req, h)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return h, resp, nil
+}
+
+// ParseRequestPayload parses the request payload. For recognized event types,
+// a value of the corresponding struct type will be returned.
+func (d *HookDelivery) ParseRequestPayload() (interface{}, error) {
+	eType, ok := eventTypeMapping[*d.Event]
+	if !ok {
+		return nil, fmt.Errorf("unsupported event type %q", *d.Event)
+	}
+
+	e := &Event{Type: &eType, RawPayload: d.Request.RawPayload}
+	return e.ParsePayload()
+}
diff --git a/go.mod b/go.mod
index b54e4324..c2d65842 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/google/go-github/v36
+module github.com/google/go-github/v37
 
 require (
 	github.com/golang/protobuf v1.3.2 // indirect


Test patch:
diff --git a/github/examples_test.go b/github/examples_test.go
index e3d6c24e..730d6228 100644
--- a/github/examples_test.go
+++ b/github/examples_test.go
@@ -12,7 +12,7 @@ import (
 	"fmt"
 	"log"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 )
 
 func ExampleClient_Markdown() {
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index 4fcdd919..03fd08ea 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -1920,6 +1920,16 @@ func TestCheckSuite_GetConclusion(tt *testing.T) {
 	c.GetConclusion()
 }
 
+func TestCheckSuite_GetCreatedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	c := &CheckSuite{CreatedAt: &zeroValue}
+	c.GetCreatedAt()
+	c = &CheckSuite{}
+	c.GetCreatedAt()
+	c = nil
+	c.GetCreatedAt()
+}
+
 func TestCheckSuite_GetHeadBranch(tt *testing.T) {
 	var zeroValue string
 	c := &CheckSuite{HeadBranch: &zeroValue}
@@ -1984,6 +1994,16 @@ func TestCheckSuite_GetStatus(tt *testing.T) {
 	c.GetStatus()
 }
 
+func TestCheckSuite_GetUpdatedAt(tt *testing.T) {
+	var zeroValue Timestamp
+	c := &CheckSuite{UpdatedAt: &zeroValue}
+	c.GetUpdatedAt()
+	c = &CheckSuite{}
+	c.GetUpdatedAt()
+	c = nil
+	c.GetUpdatedAt()
+}
+
 func TestCheckSuite_GetURL(tt *testing.T) {
 	var zeroValue string
 	c := &CheckSuite{URL: &zeroValue}
@@ -5662,6 +5682,167 @@ func TestHookConfig_GetURL(tt *testing.T) {
 	h.GetURL()
 }
 
+func TestHookDelivery_GetAction(tt *testing.T) {
+	var zeroValue string
+	h := &HookDelivery{Action: &zeroValue}
+	h.GetAction()
+	h = &HookDelivery{}
+	h.GetAction()
+	h = nil
+	h.GetAction()
+}
+
+func TestHookDelivery_GetDeliveredAt(tt *testing.T) {
+	var zeroValue Timestamp
+	h := &HookDelivery{DeliveredAt: &zeroValue}
+	h.GetDeliveredAt()
+	h = &HookDelivery{}
+	h.GetDeliveredAt()
+	h = nil
+	h.GetDeliveredAt()
+}
+
+func TestHookDelivery_GetDuration(tt *testing.T) {
+	h := &HookDelivery{}
+	h.GetDuration()
+	h = nil
+	h.GetDuration()
+}
+
+func TestHookDelivery_GetEvent(tt *testing.T) {
+	var zeroValue string
+	h := &HookDelivery{Event: &zeroValue}
+	h.GetEvent()
+	h = &HookDelivery{}
+	h.GetEvent()
+	h = nil
+	h.GetEvent()
+}
+
+func TestHookDelivery_GetGUID(tt *testing.T) {
+	var zeroValue string
+	h := &HookDelivery{GUID: &zeroValue}
+	h.GetGUID()
+	h = &HookDelivery{}
+	h.GetGUID()
+	h = nil
+	h.GetGUID()
+}
+
+func TestHookDelivery_GetID(tt *testing.T) {
+	var zeroValue int64
+	h := &HookDelivery{ID: &zeroValue}
+	h.GetID()
+	h = &HookDelivery{}
+	h.GetID()
+	h = nil
+	h.GetID()
+}
+
+func TestHookDelivery_GetInstallationID(tt *testing.T) {
+	var zeroValue string
+	h := &HookDelivery{InstallationID: &zeroValue}
+	h.GetInstallationID()
+	h = &HookDelivery{}
+	h.GetInstallationID()
+	h = nil
+	h.GetInstallationID()
+}
+
+func TestHookDelivery_GetRedelivery(tt *testing.T) {
+	var zeroValue bool
+	h := &HookDelivery{Redelivery: &zeroValue}
+	h.GetRedelivery()
+	h = &HookDelivery{}
+	h.GetRedelivery()
+	h = nil
+	h.GetRedelivery()
+}
+
+func TestHookDelivery_GetRepositoryID(tt *testing.T) {
+	var zeroValue int64
+	h := &HookDelivery{RepositoryID: &zeroValue}
+	h.GetRepositoryID()
+	h = &HookDelivery{}
+	h.GetRepositoryID()
+	h = nil
+	h.GetRepositoryID()
+}
+
+func TestHookDelivery_GetRequest(tt *testing.T) {
+	h := &HookDelivery{}
+	h.GetRequest()
+	h = nil
+	h.GetRequest()
+}
+
+func TestHookDelivery_GetResponse(tt *testing.T) {
+	h := &HookDelivery{}
+	h.GetResponse()
+	h = nil
+	h.GetResponse()
+}
+
+func TestHookDelivery_GetStatus(tt *testing.T) {
+	var zeroValue string
+	h := &HookDelivery{Status: &zeroValue}
+	h.GetStatus()
+	h = &HookDelivery{}
+	h.GetStatus()
+	h = nil
+	h.GetStatus()
+}
+
+func TestHookDelivery_GetStatusCode(tt *testing.T) {
+	var zeroValue int
+	h := &HookDelivery{StatusCode: &zeroValue}
+	h.GetStatusCode()
+	h = &HookDelivery{}
+	h.GetStatusCode()
+	h = nil
+	h.GetStatusCode()
+}
+
+func TestHookRequest_GetHeaders(tt *testing.T) {
+	zeroValue := map[string]string{}
+	h := &HookRequest{Headers: zeroValue}
+	h.GetHeaders()
+	h = &HookRequest{}
+	h.GetHeaders()
+	h = nil
+	h.GetHeaders()
+}
+
+func TestHookRequest_GetRawPayload(tt *testing.T) {
+	var zeroValue json.RawMessage
+	h := &HookRequest{RawPayload: &zeroValue}
+	h.GetRawPayload()
+	h = &HookRequest{}
+	h.GetRawPayload()
+	h = nil
+	h.GetRawPayload()
+}
+
+func TestHookResponse_GetHeaders(tt *testing.T) {
+	zeroValue := map[string]string{}
+	h := &HookResponse{Headers: zeroValue}
+	h.GetHeaders()
+	h = &HookResponse{}
+	h.GetHeaders()
+	h = nil
+	h.GetHeaders()
+}
+
+func TestHookResponse_GetRawPayload(tt *testing.T) {
+	var zeroValue json.RawMessage
+	h := &HookResponse{RawPayload: &zeroValue}
+	h.GetRawPayload()
+	h = &HookResponse{}
+	h.GetRawPayload()
+	h = nil
+	h.GetRawPayload()
+}
+
 func TestHookStats_GetActiveHooks(tt *testing.T) {
 	var zeroValue int
 	h := &HookStats{ActiveHooks: &zeroValue}
@@ -11735,6 +11916,16 @@ func TestProtectionRequest_GetAllowForcePushes(tt *testing.T) {
 	p.GetAllowForcePushes()
 }
 
+func TestProtectionRequest_GetRequiredConversationResolution(tt *testing.T) {
+	var zeroValue bool
+	p := &ProtectionRequest{RequiredConversationResolution: &zeroValue}
+	p.GetRequiredConversationResolution()
+	p = &ProtectionRequest{}
+	p.GetRequiredConversationResolution()
+	p = nil
+	p.GetRequiredConversationResolution()
+}
+
 func TestProtectionRequest_GetRequiredPullRequestReviews(tt *testing.T) {
 	p := &ProtectionRequest{}
 	p.GetRequiredPullRequestReviews()
diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go
index 952176a6..0a852a60 100644
--- a/github/github-stringify_test.go
+++ b/github/github-stringify_test.go
@@ -149,11 +149,13 @@ func TestCheckSuite_String(t *testing.T) {
 		AfterSHA:   String(""),
 		Status:     String(""),
 		Conclusion: String(""),
+		CreatedAt:  &Timestamp{},
+		UpdatedAt:  &Timestamp{},
 		App:        &App{},
 		Repository: &Repository{},
 		HeadCommit: &Commit{},
 	}
-	want := `github.CheckSuite{ID:0, NodeID:"", HeadBranch:"", HeadSHA:"", URL:"", BeforeSHA:"", AfterSHA:"", Status:"", Conclusion:"", App:github.App{}, Repository:github.Repository{}, HeadCommit:github.Commit{}}`
+	want := `github.CheckSuite{ID:0, NodeID:"", HeadBranch:"", HeadSHA:"", URL:"", BeforeSHA:"", AfterSHA:"", Status:"", Conclusion:"", CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, App:github.App{}, Repository:github.Repository{}, HeadCommit:github.Commit{}}`
 	if got := v.String(); got != want {
 		t.Errorf("CheckSuite.String = %v, want %v", got, want)
 	}
@@ -544,6 +546,28 @@ func TestHook_String(t *testing.T) {
 	}
 }
 
+func TestHookDelivery_String(t *testing.T) {
+	v := HookDelivery{
+		ID:             Int64(0),
+		GUID:           String(""),
+		DeliveredAt:    &Timestamp{},
+		Redelivery:     Bool(false),
+		Duration:       Float64(0.0),
+		Status:         String(""),
+		StatusCode:     Int(0),
+		Event:          String(""),
+		Action:         String(""),
+		InstallationID: String(""),
+		RepositoryID:   Int64(0),
+		Request:        &HookRequest{},
+		Response:       &HookResponse{},
+	}
+	want := `github.HookDelivery{ID:0, GUID:"", DeliveredAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, Redelivery:false, Duration:0, Status:"", StatusCode:0, Event:"", Action:"", InstallationID:"", RepositoryID:0, Request:github.HookRequest{}, Response:github.HookResponse{}}`
+	if got := v.String(); got != want {
+		t.Errorf("HookDelivery.String = %v, want %v", got, want)
+	}
+}
+
 func TestHookStats_String(t *testing.T) {
 	v := HookStats{
 		TotalHooks:    Int(0),
diff --git a/github/github_test.go b/github/github_test.go
index 158510ab..e7c995cc 100644
--- a/github/github_test.go
+++ b/github/github_test.go
@@ -638,6 +638,20 @@ func TestResponse_cursorPagination(t *testing.T) {
 	if got, want := response.NextPageToken, "url-encoded-next-page-token"; want != got {
 		t.Errorf("response.NextPageToken: %v, want %v", got, want)
 	}
+
+	// cursor-based pagination with "cursor" param
+	r = http.Response{
+		Header: http.Header{
+			"Link": {
+				`<https://api.github.com/?cursor=v1_12345678>; rel="next"`,
+			},
+		},
+	}
+
+	response = newResponse(&r)
+	if got, want := response.Cursor, "v1_12345678"; got != want {
+		t.Errorf("response.Cursor: %v, want %v", got, want)
+	}
 }
 
 func TestResponse_populatePageValues_invalid(t *testing.T) {
diff --git a/github/messages_test.go b/github/messages_test.go
index 5fed4a2d..9880a2a5 100644
--- a/github/messages_test.go
+++ b/github/messages_test.go
@@ -66,7 +66,7 @@ func TestValidatePayload(t *testing.T) {
 		},
 		{
 			signature:       "sha256=b1f8020f5b4cd42042f807dd939015c4a418bc1ff7f604dd55b0a19b5d953d9b",
-			signatureHeader: sha256SignatureHeader,
+			signatureHeader: SHA256SignatureHeader,
 			event:           "ping",
 			wantEvent:       "ping",
 			wantPayload:     defaultBody,
@@ -89,7 +89,7 @@ func TestValidatePayload(t *testing.T) {
 			if test.signatureHeader != "" {
 				req.Header.Set(test.signatureHeader, test.signature)
 			} else {
-				req.Header.Set(sha1SignatureHeader, test.signature)
+				req.Header.Set(SHA1SignatureHeader, test.signature)
 			}
 		}
 		req.Header.Set("Content-Type", "application/json")
@@ -120,7 +120,7 @@ func TestValidatePayload_FormGet(t *testing.T) {
 	}
 	req.PostForm = form
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-	req.Header.Set(sha1SignatureHeader, signature)
+	req.Header.Set(SHA1SignatureHeader, signature)
 
 	got, err := ValidatePayload(req, secretKey)
 	if err != nil {
@@ -131,7 +131,7 @@ func TestValidatePayload_FormGet(t *testing.T) {
 	}
 
 	// check that if payload is invalid we get error
-	req.Header.Set(sha1SignatureHeader, "invalid signature")
+	req.Header.Set(SHA1SignatureHeader, "invalid signature")
 	if _, err = ValidatePayload(req, []byte{0}); err == nil {
 		t.Error("ValidatePayload = nil, want err")
 	}
@@ -150,7 +150,7 @@ func TestValidatePayload_FormPost(t *testing.T) {
 		t.Fatalf("NewRequest: %v", err)
 	}
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-	req.Header.Set(sha1SignatureHeader, signature)
+	req.Header.Set(SHA1SignatureHeader, signature)
 
 	got, err := ValidatePayload(req, secretKey)
 	if err != nil {
@@ -161,7 +161,7 @@ func TestValidatePayload_FormPost(t *testing.T) {
 	}
 
 	// check that if payload is invalid we get error
-	req.Header.Set(sha1SignatureHeader, "invalid signature")
+	req.Header.Set(SHA1SignatureHeader, "invalid signature")
 	if _, err = ValidatePayload(req, []byte{0}); err == nil {
 		t.Error("ValidatePayload = nil, want err")
 	}
@@ -477,6 +477,18 @@ func TestParseWebHook_BadMessageType(t *testing.T) {
 	}
 }
 
+func TestValidatePayloadFromBody_UnableToParseBody(t *testing.T) {
+	if _, err := ValidatePayloadFromBody("application/x-www-form-urlencoded", bytes.NewReader([]byte(`%`)), "sha1=", []byte{}); err == nil {
+		t.Errorf("ValidatePayloadFromBody returned nil; wanted error")
+	}
+}
+
+func TestValidatePayloadFromBody_UnsupportedContentType(t *testing.T) {
+	if _, err := ValidatePayloadFromBody("invalid", bytes.NewReader([]byte(`{}`)), "sha1=", []byte{}); err == nil {
+		t.Errorf("ValidatePayloadFromBody returned nil; wanted error")
+	}
+}
+
 func TestDeliveryID(t *testing.T) {
 	id := "8970a780-244e-11e7-91ca-da3aabcb9793"
 	req, err := http.NewRequest("POST", "http://localhost", nil)
@@ -494,7 +506,7 @@ func TestDeliveryID(t *testing.T) {
 func TestWebHookType(t *testing.T) {
 	want := "yo"
 	req := &http.Request{
-		Header: http.Header{eventTypeHeader: []string{want}},
+		Header: http.Header{EventTypeHeader: []string{want}},
 	}
 	if got := WebHookType(req); got != want {
 		t.Errorf("WebHookType = %q, want %q", got, want)
diff --git a/github/orgs_hooks_deliveries_test.go b/github/orgs_hooks_deliveries_test.go
new file mode 100644
index 00000000..c1efc527
--- /dev/null
+++ b/github/orgs_hooks_deliveries_test.go
@@ -0,0 +1,106 @@
+// Copyright 2021 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 (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestOrganizationsService_ListHookDeliveries(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/hooks/1/deliveries", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testFormValues(t, r, values{"cursor": "v1_12077215967"})
+		fmt.Fprint(w, `[{"id":1}, {"id":2}]`)
+	})
+
+	opt := &ListCursorOptions{Cursor: "v1_12077215967"}
+
+	ctx := context.Background()
+	hooks, _, err := client.Organizations.ListHookDeliveries(ctx, "o", 1, opt)
+	if err != nil {
+		t.Errorf("Organizations.ListHookDeliveries returned error: %v", err)
+	}
+
+	want := []*HookDelivery{{ID: Int64(1)}, {ID: Int64(2)}}
+	if d := cmp.Diff(hooks, want); d != "" {
+		t.Errorf("Organizations.ListHooks want (-), got (+):\n%s", d)
+	}
+
+	const methodName = "ListHookDeliveries"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Organizations.ListHookDeliveries(ctx, "\n", -1, opt)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.ListHookDeliveries(ctx, "o", 1, opt)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_ListHookDeliveries_invalidOwner(t *testing.T) {
+	client, _, _, teardown := setup()
+	defer teardown()
+
+	ctx := context.Background()
+	_, _, err := client.Organizations.ListHookDeliveries(ctx, "%", 1, nil)
+	testURLParseError(t, err)
+}
+
+func TestOrganizationsService_GetHookDelivery(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/hooks/1/deliveries/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{"id":1}`)
+	})
+
+	ctx := context.Background()
+	hook, _, err := client.Organizations.GetHookDelivery(ctx, "o", 1, 1)
+	if err != nil {
+		t.Errorf("Organizations.GetHookDelivery returned error: %v", err)
+	}
+
+	want := &HookDelivery{ID: Int64(1)}
+	if !cmp.Equal(hook, want) {
+		t.Errorf("Organizations.GetHookDelivery returned %+v, want %+v", hook, want)
+	}
+
+	const methodName = "GetHookDelivery"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Organizations.GetHookDelivery(ctx, "\n", -1, -1)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Organizations.GetHookDelivery(ctx, "o", 1, 1)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestOrganizationsService_GetHookDelivery_invalidOwner(t *testing.T) {
+	client, _, _, teardown := setup()
+	defer teardown()
+
+	ctx := context.Background()
+	_, _, err := client.Organizations.GetHookDelivery(ctx, "%", 1, 1)
+	testURLParseError(t, err)
+}
diff --git a/github/repos_hooks_deliveries_test.go b/github/repos_hooks_deliveries_test.go
new file mode 100644
index 00000000..5c6fb86d
--- /dev/null
+++ b/github/repos_hooks_deliveries_test.go
@@ -0,0 +1,220 @@
+// Copyright 2021 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 (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestRepositoriesService_ListHookDeliveries(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/hooks/1/deliveries", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testFormValues(t, r, values{"cursor": "v1_12077215967"})
+		fmt.Fprint(w, `[{"id":1}, {"id":2}]`)
+	})
+
+	opt := &ListCursorOptions{Cursor: "v1_12077215967"}
+
+	ctx := context.Background()
+	hooks, _, err := client.Repositories.ListHookDeliveries(ctx, "o", "r", 1, opt)
+	if err != nil {
+		t.Errorf("Repositories.ListHookDeliveries returned error: %v", err)
+	}
+
+	want := []*HookDelivery{{ID: Int64(1)}, {ID: Int64(2)}}
+	if d := cmp.Diff(hooks, want); d != "" {
+		t.Errorf("Repositories.ListHooks want (-), got (+):\n%s", d)
+	}
+
+	const methodName = "ListHookDeliveries"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Repositories.ListHookDeliveries(ctx, "\n", "\n", -1, opt)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.ListHookDeliveries(ctx, "o", "r", 1, opt)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_ListHookDeliveries_invalidOwner(t *testing.T) {
+	client, _, _, teardown := setup()
+	defer teardown()
+
+	ctx := context.Background()
+	_, _, err := client.Repositories.ListHookDeliveries(ctx, "%", "%", 1, nil)
+	testURLParseError(t, err)
+}
+
+func TestRepositoriesService_GetHookDelivery(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/hooks/1/deliveries/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		fmt.Fprint(w, `{"id":1}`)
+	})
+
+	ctx := context.Background()
+	hook, _, err := client.Repositories.GetHookDelivery(ctx, "o", "r", 1, 1)
+	if err != nil {
+		t.Errorf("Repositories.GetHookDelivery returned error: %v", err)
+	}
+
+	want := &HookDelivery{ID: Int64(1)}
+	if !cmp.Equal(hook, want) {
+		t.Errorf("Repositories.GetHookDelivery returned %+v, want %+v", hook, want)
+	}
+
+	const methodName = "GetHookDelivery"
+	testBadOptions(t, methodName, func() (err error) {
+		_, _, err = client.Repositories.GetHookDelivery(ctx, "\n", "\n", -1, -1)
+		return err
+	})
+
+	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
+		got, resp, err := client.Repositories.GetHookDelivery(ctx, "o", "r", 1, 1)
+		if got != nil {
+			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
+		}
+		return resp, err
+	})
+}
+
+func TestRepositoriesService_GetHookDelivery_invalidOwner(t *testing.T) {
+	client, _, _, teardown := setup()
+	defer teardown()
+
+	ctx := context.Background()
+	_, _, err := client.Repositories.GetHookDelivery(ctx, "%", "%", 1, 1)
+	testURLParseError(t, err)
+}
+
+var hookDeliveryPayloadTypeToStruct = map[string]interface{}{
+	"check_run":                      &CheckRunEvent{},
+	"check_suite":                    &CheckSuiteEvent{},
+	"commit_comment":                 &CommitCommentEvent{},
+	"content_reference":              &ContentReferenceEvent{},
+	"create":                         &CreateEvent{},
+	"delete":                         &DeleteEvent{},
+	"deploy_key":                     &DeployKeyEvent{},
+	"deployment":                     &DeploymentEvent{},
+	"deployment_status":              &DeploymentStatusEvent{},
+	"fork":                           &ForkEvent{},
+	"github_app_authorization":       &GitHubAppAuthorizationEvent{},
+	"gollum":                         &GollumEvent{},
+	"installation":                   &InstallationEvent{},
+	"installation_repositories":      &InstallationRepositoriesEvent{},
+	"issue_comment":                  &IssueCommentEvent{},
+	"issues":                         &IssuesEvent{},
+	"label":                          &LabelEvent{},
+	"marketplace_purchase":           &MarketplacePurchaseEvent{},
+	"member":                         &MemberEvent{},
+	"membership":                     &MembershipEvent{},
+	"meta":                           &MetaEvent{},
+	"milestone":                      &MilestoneEvent{},
+	"organization":                   &OrganizationEvent{},
+	"org_block":                      &OrgBlockEvent{},
+	"package":                        &PackageEvent{},
+	"page_build":                     &PageBuildEvent{},
+	"ping":                           &PingEvent{},
+	"project":                        &ProjectEvent{},
+	"project_card":                   &ProjectCardEvent{},
+	"project_column":                 &ProjectColumnEvent{},
+	"public":                         &PublicEvent{},
+	"pull_request":                   &PullRequestEvent{},
+	"pull_request_review":            &PullRequestReviewEvent{},
+	"pull_request_review_comment":    &PullRequestReviewCommentEvent{},
+	"pull_request_target":            &PullRequestTargetEvent{},
+	"push":                           &PushEvent{},
+	"release":                        &ReleaseEvent{},
+	"repository":                     &RepositoryEvent{},
+	"repository_dispatch":            &RepositoryDispatchEvent{},
+	"repository_vulnerability_alert": &RepositoryVulnerabilityAlertEvent{},
+	"star":                           &StarEvent{},
+	"status":                         &StatusEvent{},
+	"team":                           &TeamEvent{},
+	"team_add":                       &TeamAddEvent{},
+	"user":                           &UserEvent{},
+	"watch":                          &WatchEvent{},
+	"workflow_dispatch":              &WorkflowDispatchEvent{},
+	"workflow_run":                   &WorkflowRunEvent{},
+}
+
+func TestHookDelivery_ParsePayload(t *testing.T) {
+	for evt, obj := range hookDeliveryPayloadTypeToStruct {
+		t.Run(evt, func(t *testing.T) {
+			bs, err := json.Marshal(obj)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			p := json.RawMessage(bs)
+
+			d := &HookDelivery{
+				Event: String(evt),
+				Request: &HookRequest{
+					RawPayload: &p,
+				},
+			}
+
+			got, err := d.ParseRequestPayload()
+			if err != nil {
+				t.Error(err)
+			}
+
+			if !reflect.DeepEqual(obj, got) {
+				t.Errorf("want %T %v, got %T %v", obj, obj, got, got)
+			}
+		})
+	}
+}
+
+func TestHookDelivery_ParsePayload_invalidEvent(t *testing.T) {
+	p := json.RawMessage(nil)
+
+	d := &HookDelivery{
+		Event: String("some_invalid_event"),
+		Request: &HookRequest{
+			RawPayload: &p,
+		},
+	}
+
+	_, err := d.ParseRequestPayload()
+	if err == nil || err.Error() != `unsupported event type "some_invalid_event"` {
+		t.Errorf("unexpected error: %v", err)
+	}
+}
+
+func TestHookDelivery_ParsePayload_invalidPayload(t *testing.T) {
+	p := json.RawMessage([]byte(`{"check_run":{"id":"invalid"}}`))
+
+	d := &HookDelivery{
+		Event: String("check_run"),
+		Request: &HookRequest{
+			RawPayload: &p,
+		},
+	}
+
+	_, err := d.ParseRequestPayload()
+	if err == nil || err.Error() != "json: cannot unmarshal string into Go struct field CheckRun.check_run.id of type int64" {
+		t.Errorf("unexpected error: %v", err)
+	}
+}
diff --git a/test/fields/fields.go b/test/fields/fields.go
index de606ab8..149a3e07 100644
--- a/test/fields/fields.go
+++ b/test/fields/fields.go
@@ -25,7 +25,7 @@ import (
 	"reflect"
 	"strings"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 	"golang.org/x/oauth2"
 )
 
diff --git a/test/integration/activity_test.go b/test/integration/activity_test.go
index 0f8c64c3..d1b8b501 100644
--- a/test/integration/activity_test.go
+++ b/test/integration/activity_test.go
@@ -12,7 +12,7 @@ import (
 	"context"
 	"testing"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 )
 
 const (
diff --git a/test/integration/authorizations_test.go b/test/integration/authorizations_test.go
index d7f516ba..0841ffc4 100644
--- a/test/integration/authorizations_test.go
+++ b/test/integration/authorizations_test.go
@@ -17,7 +17,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 )
 
 const msgEnvMissing = "Skipping test because the required environment variable (%v) is not present."
diff --git a/test/integration/github_test.go b/test/integration/github_test.go
index 405eff74..5dd6b4b4 100644
--- a/test/integration/github_test.go
+++ b/test/integration/github_test.go
@@ -15,7 +15,7 @@ import (
 	"net/http"
 	"os"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 	"golang.org/x/oauth2"
 )
 
diff --git a/test/integration/repos_test.go b/test/integration/repos_test.go
index 5bbd762f..ddaae4a0 100644
--- a/test/integration/repos_test.go
+++ b/test/integration/repos_test.go
@@ -16,7 +16,7 @@ import (
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 )
 
 func TestRepositories_CRUD(t *testing.T) {
diff --git a/test/integration/users_test.go b/test/integration/users_test.go
index 6ea451e5..f983d0db 100644
--- a/test/integration/users_test.go
+++ b/test/integration/users_test.go
@@ -14,7 +14,7 @@ import (
 	"math/rand"
 	"testing"
 
-	"github.com/google/go-github/v36/github"
+	"github.com/google/go-github/v37/github"
 )
 
 func TestUsers_Get(t *testing.T) {