Repository: google/go-github
Pull request #1961 (2021-07-10 15:41:21)

Add test cases for JSON resource marshaling
Resource Covered :

* FeedLinks
* Feeds
* FeedLink

Helps with issue: #55

codecov[bot]: # [Codecov](https://codecov.io/gh/google/go-github/pull/1961?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#1961](https://codecov.io/gh/google/go-github/pull/1961?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (3b74056) into [master](https://codecov.io/gh/google/go-github/commit/f47f8fe25bcaf870974ff18ae62022c32c164966?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (f47f8fe) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/google/go-github/pull/1961/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/1961?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)

```diff
@@           Coverage Diff           @@
##           master    #1961   +/-   ##
=======================================
  Coverage   97.84%   97.84%           
=======================================
  Files         105      105           
  Lines        6809     6809           
=======================================
  Hits         6662     6662           
  Misses         80       80           
  Partials       67       67           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/google/go-github/pull/1961?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/1961?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Last update [f47f8fe...3b74056](https://codecov.io/gh/google/go-github/pull/1961?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).


Patch:
diff --git a/example/commitpr/main.go b/example/commitpr/main.go
index 33572f20..d222b88f 100644
--- a/example/commitpr/main.go
+++ b/example/commitpr/main.go
@@ -125,7 +125,7 @@ func getFileContent(fileArg string) (targetName string, b []byte, err error) {
 // pushCommit creates the commit in the given reference using the given tree.
 func pushCommit(ref *github.Reference, tree *github.Tree) (err error) {
 	// Get the parent commit to attach the commit to.
-	parent, _, err := client.Repositories.GetCommit(ctx, *sourceOwner, *sourceRepo, *ref.Object.SHA)
+	parent, _, err := client.Repositories.GetCommit(ctx, *sourceOwner, *sourceRepo, *ref.Object.SHA, nil)
 	if err != nil {
 		return err
 	}
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/github-accessors.go b/github/github-accessors.go
index d5216f52..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 {
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_commits.go b/github/repos_commits.go
index b49a5afe..ce3b48e3 100644
--- a/github/repos_commits.go
+++ b/github/repos_commits.go
@@ -150,8 +150,12 @@ func (s *RepositoriesService) ListCommits(ctx context.Context, owner, repo strin
 //
 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#get-a-single-commit
 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#get-a-commit
-func (s *RepositoriesService) GetCommit(ctx context.Context, owner, repo, sha string) (*RepositoryCommit, *Response, error) {
+func (s *RepositoriesService) GetCommit(ctx context.Context, owner, repo, sha string, opts *ListOptions) (*RepositoryCommit, *Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/commits/%v", owner, repo, sha)
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
 
 	req, err := s.client.NewRequest("GET", u, nil)
 	if err != nil {
@@ -224,11 +228,15 @@ func (s *RepositoriesService) GetCommitSHA1(ctx context.Context, owner, repo, re
 // CompareCommits compares a range of commits with each other.
 //
 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#compare-two-commits
-func (s *RepositoriesService) CompareCommits(ctx context.Context, owner, repo string, base, head string) (*CommitsComparison, *Response, error) {
+func (s *RepositoriesService) CompareCommits(ctx context.Context, owner, repo string, base, head string, opts *ListOptions) (*CommitsComparison, *Response, error) {
 	escapedBase := url.QueryEscape(base)
 	escapedHead := url.QueryEscape(head)
 
 	u := fmt.Sprintf("repos/%v/%v/compare/%v...%v", owner, repo, escapedBase, escapedHead)
+	u, err := addOptions(u, opts)
+	if err != nil {
+		return nil, nil, err
+	}
 
 	req, err := s.client.NewRequest("GET", u, nil)
 	if err != nil {
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()
+}


Test patch:
diff --git a/github/activity_test.go b/github/activity_test.go
index 3d774279..ef7cf32f 100644
--- a/github/activity_test.go
+++ b/github/activity_test.go
@@ -130,3 +130,181 @@ var wantFeeds = &Feeds{
 		},
 	},
 }
+
+func TestFeedLink_Marshal(t *testing.T) {
+	testJSONMarshal(t, &FeedLink{}, "{}")
+
+	u := &FeedLink{
+		HRef: String("h"),
+		Type: String("t"),
+	}
+
+	want := `{
+		"href": "h",
+		"type": "t"
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestFeeds_Marshal(t *testing.T) {
+	testJSONMarshal(t, &Feeds{}, "{}")
+
+	u := &Feeds{
+		TimelineURL:                 String("t"),
+		UserURL:                     String("u"),
+		CurrentUserPublicURL:        String("cupu"),
+		CurrentUserURL:              String("cuu"),
+		CurrentUserActorURL:         String("cuau"),
+		CurrentUserOrganizationURL:  String("cuou"),
+		CurrentUserOrganizationURLs: []string{"a"},
+		Links: &FeedLinks{
+			Timeline: &FeedLink{
+				HRef: String("h"),
+				Type: String("t"),
+			},
+			User: &FeedLink{
+				HRef: String("h"),
+				Type: String("t"),
+			},
+			CurrentUserPublic: &FeedLink{
+				HRef: String("h"),
+				Type: String("t"),
+			},
+			CurrentUser: &FeedLink{
+				HRef: String("h"),
+				Type: String("t"),
+			},
+			CurrentUserActor: &FeedLink{
+				HRef: String("h"),
+				Type: String("t"),
+			},
+			CurrentUserOrganization: &FeedLink{
+				HRef: String("h"),
+				Type: String("t"),
+			},
+			CurrentUserOrganizations: []*FeedLink{
+				{
+					HRef: String("h"),
+					Type: String("t"),
+				},
+			},
+		},
+	}
+
+	want := `{
+		"timeline_url": "t",
+		"user_url": "u",
+		"current_user_public_url": "cupu",
+		"current_user_url": "cuu",
+		"current_user_actor_url": "cuau",
+		"current_user_organization_url": "cuou",
+		"current_user_organization_urls": ["a"],
+		"_links": {
+			"timeline": {
+				"href": "h",
+				"type": "t"
+				},
+			"user": {
+				"href": "h",
+				"type": "t"
+			},
+			"current_user_public": {
+				"href": "h",
+				"type": "t"
+			},
+			"current_user": {
+				"href": "h",
+				"type": "t"
+			},
+			"current_user_actor": {
+				"href": "h",
+				"type": "t"
+			},
+			"current_user_organization": {
+				"href": "h",
+				"type": "t"
+			},
+			"current_user_organizations": [
+				{
+					"href": "h",
+					"type": "t"
+				}
+			]
+		}
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
+func TestFeedLinks_Marshal(t *testing.T) {
+	testJSONMarshal(t, &FeedLinks{}, "{}")
+
+	u := &FeedLinks{
+		Timeline: &FeedLink{
+			HRef: String("h"),
+			Type: String("t"),
+		},
+		User: &FeedLink{
+			HRef: String("h"),
+			Type: String("t"),
+		},
+		CurrentUserPublic: &FeedLink{
+			HRef: String("h"),
+			Type: String("t"),
+		},
+		CurrentUser: &FeedLink{
+			HRef: String("h"),
+			Type: String("t"),
+		},
+		CurrentUserActor: &FeedLink{
+			HRef: String("h"),
+			Type: String("t"),
+		},
+		CurrentUserOrganization: &FeedLink{
+			HRef: String("h"),
+			Type: String("t"),
+		},
+		CurrentUserOrganizations: []*FeedLink{
+			{
+				HRef: String("h"),
+				Type: String("t"),
+			},
+		},
+	}
+
+	want := `{
+		"timeline": {
+			"href": "h",
+			"type": "t"
+		},
+		"user": {
+			"href": "h",
+			"type": "t"
+		},
+		"current_user_public": {
+			"href": "h",
+			"type": "t"
+		},
+		"current_user": {
+			"href": "h",
+			"type": "t"
+		},
+		"current_user_actor": {
+			"href": "h",
+			"type": "t"
+		},
+		"current_user_organization": {
+			"href": "h",
+			"type": "t"
+		},
+		"current_user_organizations": [
+			{
+				"href": "h",
+				"type": "t"
+			}
+		]
+	}`
+
+	testJSONMarshal(t, u, want)
+}
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index b63e9709..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}
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_commits_test.go b/github/repos_commits_test.go
index 430c03d6..b654b913 100644
--- a/github/repos_commits_test.go
+++ b/github/repos_commits_test.go
@@ -74,6 +74,7 @@ func TestRepositoriesService_GetCommit(t *testing.T) {
 
 	mux.HandleFunc("/repos/o/r/commits/s", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
+		testFormValues(t, r, values{"per_page": "2", "page": "2"})
 		fmt.Fprintf(w, `{
 		  "sha": "s",
 		  "commit": { "message": "m" },
@@ -97,8 +98,9 @@ func TestRepositoriesService_GetCommit(t *testing.T) {
 		}`)
 	})
 
+	opts := &ListOptions{Page: 2, PerPage: 2}
 	ctx := context.Background()
-	commit, _, err := client.Repositories.GetCommit(ctx, "o", "r", "s")
+	commit, _, err := client.Repositories.GetCommit(ctx, "o", "r", "s", opts)
 	if err != nil {
 		t.Errorf("Repositories.GetCommit returned error: %v", err)
 	}
@@ -144,12 +146,12 @@ func TestRepositoriesService_GetCommit(t *testing.T) {
 
 	const methodName = "GetCommit"
 	testBadOptions(t, methodName, func() (err error) {
-		_, _, err = client.Repositories.GetCommit(ctx, "\n", "\n", "\n")
+		_, _, err = client.Repositories.GetCommit(ctx, "\n", "\n", "\n", opts)
 		return err
 	})
 
 	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
-		got, resp, err := client.Repositories.GetCommit(ctx, "o", "r", "s")
+		got, resp, err := client.Repositories.GetCommit(ctx, "o", "r", "s", opts)
 		if got != nil {
 			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
 		}
@@ -389,6 +391,7 @@ func TestRepositoriesService_CompareCommits(t *testing.T) {
 
 		mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
 			testMethod(t, r, "GET")
+			testFormValues(t, r, values{"per_page": "2", "page": "2"})
 			fmt.Fprintf(w, `{
 		  "base_commit": {
 		    "sha": "s",
@@ -424,8 +427,9 @@ func TestRepositoriesService_CompareCommits(t *testing.T) {
 		}`, escapedBase, escapedHead)
 		})
 
+		opts := &ListOptions{Page: 2, PerPage: 2}
 		ctx := context.Background()
-		got, _, err := client.Repositories.CompareCommits(ctx, "o", "r", base, head)
+		got, _, err := client.Repositories.CompareCommits(ctx, "o", "r", base, head, opts)
 		if err != nil {
 			t.Errorf("Repositories.CompareCommits returned error: %v", err)
 		}
@@ -484,12 +488,12 @@ func TestRepositoriesService_CompareCommits(t *testing.T) {
 
 		const methodName = "CompareCommits"
 		testBadOptions(t, methodName, func() (err error) {
-			_, _, err = client.Repositories.CompareCommits(ctx, "\n", "\n", "\n", "\n")
+			_, _, err = client.Repositories.CompareCommits(ctx, "\n", "\n", "\n", "\n", opts)
 			return err
 		})
 
 		testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
-			got, resp, err := client.Repositories.CompareCommits(ctx, "o", "r", base, head)
+			got, resp, err := client.Repositories.CompareCommits(ctx, "o", "r", base, head, opts)
 			if got != nil {
 				t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
 			}
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)
+	}
+}