Repository: xanzy/go-gitlab
Pull request #1082 (2021-02-25 23:06:55)

Add support for the Release webhook
See docs at https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#release-events

The `release.json` is an actual webhook payload from a few hours ago, the only thing I manually added was the `Assets.Links` field from the docs so I could test it

I also added the `releases_events` to various hook options. The only part I left as it is, is the [Services API](https://docs.gitlab.com/ee/api/services.html) since I couldn't find out whether it supported the flag, so I left it for now. All other changed structs are documented


Patch:
diff --git a/event_parsing.go b/event_parsing.go
index 6585db8..3c03ed1 100644
--- a/event_parsing.go
+++ b/event_parsing.go
@@ -37,6 +37,7 @@ const (
 	EventConfidentialNote  EventType = "Confidential Note Hook"
 	EventTypePipeline      EventType = "Pipeline Hook"
 	EventTypePush          EventType = "Push Hook"
+	EventTypeRelease       EventType = "Release Hook"
 	EventTypeSystemHook    EventType = "System Hook"
 	EventTypeTagPush       EventType = "Tag Push Hook"
 	EventTypeWikiPage      EventType = "Wiki Page Hook"
@@ -212,6 +213,8 @@ func ParseWebhook(eventType EventType, payload []byte) (event interface{}, err e
 		event = &PipelineEvent{}
 	case EventTypePush:
 		event = &PushEvent{}
+	case EventTypeRelease:
+		event = &ReleaseEvent{}
 	case EventTypeTagPush:
 		event = &TagEvent{}
 	case EventTypeWikiPage:
diff --git a/event_webhook_types.go b/event_webhook_types.go
index c8cf0cc..0e29ec9 100644
--- a/event_webhook_types.go
+++ b/event_webhook_types.go
@@ -23,23 +23,54 @@ import (
 	"time"
 )
 
-// PushEvent represents a push event.
+//BuildEvent represents a build event
 //
 // GitLab API docs:
-// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#push-events
-type PushEvent struct {
-	ObjectKind   string `json:"object_kind"`
-	Before       string `json:"before"`
-	After        string `json:"after"`
-	Ref          string `json:"ref"`
-	CheckoutSHA  string `json:"checkout_sha"`
-	UserID       int    `json:"user_id"`
-	UserName     string `json:"user_name"`
-	UserUsername string `json:"user_username"`
-	UserEmail    string `json:"user_email"`
-	UserAvatar   string `json:"user_avatar"`
-	ProjectID    int    `json:"project_id"`
-	Project      struct {
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#build-events
+type BuildEvent struct {
+	ObjectKind        string  `json:"object_kind"`
+	Ref               string  `json:"ref"`
+	Tag               bool    `json:"tag"`
+	BeforeSHA         string  `json:"before_sha"`
+	SHA               string  `json:"sha"`
+	BuildID           int     `json:"build_id"`
+	BuildName         string  `json:"build_name"`
+	BuildStage        string  `json:"build_stage"`
+	BuildStatus       string  `json:"build_status"`
+	BuildStartedAt    string  `json:"build_started_at"`
+	BuildFinishedAt   string  `json:"build_finished_at"`
+	BuildDuration     float64 `json:"build_duration"`
+	BuildAllowFailure bool    `json:"build_allow_failure"`
+	ProjectID         int     `json:"project_id"`
+	ProjectName       string  `json:"project_name"`
+	User              struct {
+		ID    int    `json:"id"`
+		Name  string `json:"name"`
+		Email string `json:"email"`
+	} `json:"user"`
+	Commit struct {
+		ID          int    `json:"id"`
+		SHA         string `json:"sha"`
+		Message     string `json:"message"`
+		AuthorName  string `json:"author_name"`
+		AuthorEmail string `json:"author_email"`
+		Status      string `json:"status"`
+		Duration    int    `json:"duration"`
+		StartedAt   string `json:"started_at"`
+		FinishedAt  string `json:"finished_at"`
+	} `json:"commit"`
+	Repository *Repository `json:"repository"`
+}
+
+// CommitCommentEvent represents a comment on a commit event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#comment-on-commit
+type CommitCommentEvent struct {
+	ObjectKind string `json:"object_kind"`
+	User       *User  `json:"user"`
+	ProjectID  int    `json:"project_id"`
+	Project    struct {
 		Name              string          `json:"name"`
 		Description       string          `json:"description"`
 		AvatarURL         string          `json:"avatar_url"`
@@ -55,8 +86,32 @@ type PushEvent struct {
 		WebURL            string          `json:"web_url"`
 		Visibility        VisibilityValue `json:"visibility"`
 	} `json:"project"`
-	Repository *Repository `json:"repository"`
-	Commits    []*struct {
+	Repository       *Repository `json:"repository"`
+	ObjectAttributes struct {
+		ID           int    `json:"id"`
+		Note         string `json:"note"`
+		NoteableType string `json:"noteable_type"`
+		AuthorID     int    `json:"author_id"`
+		CreatedAt    string `json:"created_at"`
+		UpdatedAt    string `json:"updated_at"`
+		ProjectID    int    `json:"project_id"`
+		Attachment   string `json:"attachment"`
+		LineCode     string `json:"line_code"`
+		CommitID     string `json:"commit_id"`
+		NoteableID   int    `json:"noteable_id"`
+		System       bool   `json:"system"`
+		StDiff       struct {
+			Diff        string `json:"diff"`
+			NewPath     string `json:"new_path"`
+			OldPath     string `json:"old_path"`
+			AMode       string `json:"a_mode"`
+			BMode       string `json:"b_mode"`
+			NewFile     bool   `json:"new_file"`
+			RenamedFile bool   `json:"renamed_file"`
+			DeletedFile bool   `json:"deleted_file"`
+		} `json:"st_diff"`
+	} `json:"object_attributes"`
+	Commit *struct {
 		ID        string     `json:"id"`
 		Message   string     `json:"message"`
 		Timestamp *time.Time `json:"timestamp"`
@@ -65,30 +120,58 @@ type PushEvent struct {
 			Name  string `json:"name"`
 			Email string `json:"email"`
 		} `json:"author"`
-		Added    []string `json:"added"`
-		Modified []string `json:"modified"`
-		Removed  []string `json:"removed"`
-	} `json:"commits"`
-	TotalCommitsCount int `json:"total_commits_count"`
+	} `json:"commit"`
 }
 
-// TagEvent represents a tag event.
+// DeploymentEvent represents a deployment event
 //
 // GitLab API docs:
-// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#tag-events
-type TagEvent struct {
-	ObjectKind  string `json:"object_kind"`
-	Before      string `json:"before"`
-	After       string `json:"after"`
-	Ref         string `json:"ref"`
-	CheckoutSHA string `json:"checkout_sha"`
-	UserID      int    `json:"user_id"`
-	UserName    string `json:"user_name"`
-	UserAvatar  string `json:"user_avatar"`
-	UserEmail   string `json:"user_email"`
-	ProjectID   int    `json:"project_id"`
-	Message     string `json:"message"`
-	Project     struct {
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#deployment-events
+type DeploymentEvent struct {
+	ObjectKind    string `json:"object_kind"`
+	Status        string `json:"status"`
+	DeployableID  int    `json:"deployable_id"`
+	DeployableURL string `json:"deployable_url"`
+	Environment   string `json:"environment"`
+	Project       struct {
+		ID                int     `json:"id"`
+		Name              string  `json:"name"`
+		Description       string  `json:"description"`
+		WebURL            string  `json:"web_url"`
+		AvatarURL         *string `json:"avatar_url"`
+		GitSSHURL         string  `json:"git_ssh_url"`
+		GitHTTPURL        string  `json:"git_http_url"`
+		Namespace         string  `json:"namespace"`
+		VisibilityLevel   int     `json:"visibility_level"`
+		PathWithNamespace string  `json:"path_with_namespace"`
+		DefaultBranch     string  `json:"default_branch"`
+		CIConfigPath      string  `json:"ci_config_path"`
+		Homepage          string  `json:"homepage"`
+		URL               string  `json:"url"`
+		SSHURL            string  `json:"ssh_url"`
+		HTTPURL           string  `json:"http_url"`
+	} `json:"project"`
+	ShortSHA string `json:"short_sha"`
+	User     struct {
+		Name      string `json:"name"`
+		Username  string `json:"username"`
+		AvatarURL string `json:"avatar_url"`
+		Email     string `json:"email"`
+	} `json:"user"`
+	UserURL     string `json:"user_url"`
+	CommitURL   string `json:"commit_url"`
+	CommitTitle string `json:"commit_title"`
+}
+
+// IssueCommentEvent represents a comment on an issue event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#comment-on-issue
+type IssueCommentEvent struct {
+	ObjectKind string `json:"object_kind"`
+	User       *User  `json:"user"`
+	ProjectID  int    `json:"project_id"`
+	Project    struct {
 		Name              string          `json:"name"`
 		Description       string          `json:"description"`
 		AvatarURL         string          `json:"avatar_url"`
@@ -104,21 +187,48 @@ type TagEvent struct {
 		WebURL            string          `json:"web_url"`
 		Visibility        VisibilityValue `json:"visibility"`
 	} `json:"project"`
-	Repository *Repository `json:"repository"`
-	Commits    []*struct {
-		ID        string     `json:"id"`
-		Message   string     `json:"message"`
-		Timestamp *time.Time `json:"timestamp"`
-		URL       string     `json:"url"`
-		Author    struct {
-			Name  string `json:"name"`
-			Email string `json:"email"`
-		} `json:"author"`
-		Added    []string `json:"added"`
-		Modified []string `json:"modified"`
-		Removed  []string `json:"removed"`
-	} `json:"commits"`
-	TotalCommitsCount int `json:"total_commits_count"`
+	Repository       *Repository `json:"repository"`
+	ObjectAttributes struct {
+		ID           int     `json:"id"`
+		Note         string  `json:"note"`
+		NoteableType string  `json:"noteable_type"`
+		AuthorID     int     `json:"author_id"`
+		CreatedAt    string  `json:"created_at"`
+		UpdatedAt    string  `json:"updated_at"`
+		ProjectID    int     `json:"project_id"`
+		Attachment   string  `json:"attachment"`
+		LineCode     string  `json:"line_code"`
+		CommitID     string  `json:"commit_id"`
+		NoteableID   int     `json:"noteable_id"`
+		System       bool    `json:"system"`
+		StDiff       []*Diff `json:"st_diff"`
+		URL          string  `json:"url"`
+	} `json:"object_attributes"`
+	Issue struct {
+		ID                  int      `json:"id"`
+		IID                 int      `json:"iid"`
+		ProjectID           int      `json:"project_id"`
+		MilestoneID         int      `json:"milestone_id"`
+		AuthorID            int      `json:"author_id"`
+		Description         string   `json:"description"`
+		State               string   `json:"state"`
+		Title               string   `json:"title"`
+		LastEditedAt        string   `json:"last_edit_at"`
+		LastEditedByID      int      `json:"last_edited_by_id"`
+		UpdatedAt           string   `json:"updated_at"`
+		UpdatedByID         int      `json:"updated_by_id"`
+		CreatedAt           string   `json:"created_at"`
+		ClosedAt            string   `json:"closed_at"`
+		DueDate             *ISOTime `json:"due_date"`
+		URL                 string   `json:"url"`
+		TimeEstimate        int      `json:"time_estimate"`
+		Confidential        bool     `json:"confidential"`
+		TotalTimeSpent      int      `json:"total_time_spent"`
+		HumanTotalTimeSpent string   `json:"human_total_time_spent"`
+		HumanTimeEstimate   string   `json:"human_time_estimate"`
+		AssigneeIDs         []int    `json:"assignee_ids"`
+		AssigneeID          int      `json:"assignee_id"`
+	} `json:"issue"`
 }
 
 // IssueEvent represents a issue event.
@@ -247,72 +357,11 @@ type JobEvent struct {
 	} `json:"runner"`
 }
 
-// CommitCommentEvent represents a comment on a commit event.
+// MergeCommentEvent represents a comment on a merge event.
 //
 // GitLab API docs:
-// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#comment-on-commit
-type CommitCommentEvent struct {
-	ObjectKind string `json:"object_kind"`
-	User       *User  `json:"user"`
-	ProjectID  int    `json:"project_id"`
-	Project    struct {
-		Name              string          `json:"name"`
-		Description       string          `json:"description"`
-		AvatarURL         string          `json:"avatar_url"`
-		GitSSHURL         string          `json:"git_ssh_url"`
-		GitHTTPURL        string          `json:"git_http_url"`
-		Namespace         string          `json:"namespace"`
-		PathWithNamespace string          `json:"path_with_namespace"`
-		DefaultBranch     string          `json:"default_branch"`
-		Homepage          string          `json:"homepage"`
-		URL               string          `json:"url"`
-		SSHURL            string          `json:"ssh_url"`
-		HTTPURL           string          `json:"http_url"`
-		WebURL            string          `json:"web_url"`
-		Visibility        VisibilityValue `json:"visibility"`
-	} `json:"project"`
-	Repository       *Repository `json:"repository"`
-	ObjectAttributes struct {
-		ID           int    `json:"id"`
-		Note         string `json:"note"`
-		NoteableType string `json:"noteable_type"`
-		AuthorID     int    `json:"author_id"`
-		CreatedAt    string `json:"created_at"`
-		UpdatedAt    string `json:"updated_at"`
-		ProjectID    int    `json:"project_id"`
-		Attachment   string `json:"attachment"`
-		LineCode     string `json:"line_code"`
-		CommitID     string `json:"commit_id"`
-		NoteableID   int    `json:"noteable_id"`
-		System       bool   `json:"system"`
-		StDiff       struct {
-			Diff        string `json:"diff"`
-			NewPath     string `json:"new_path"`
-			OldPath     string `json:"old_path"`
-			AMode       string `json:"a_mode"`
-			BMode       string `json:"b_mode"`
-			NewFile     bool   `json:"new_file"`
-			RenamedFile bool   `json:"renamed_file"`
-			DeletedFile bool   `json:"deleted_file"`
-		} `json:"st_diff"`
-	} `json:"object_attributes"`
-	Commit *struct {
-		ID        string     `json:"id"`
-		Message   string     `json:"message"`
-		Timestamp *time.Time `json:"timestamp"`
-		URL       string     `json:"url"`
-		Author    struct {
-			Name  string `json:"name"`
-			Email string `json:"email"`
-		} `json:"author"`
-	} `json:"commit"`
-}
-
-// MergeCommentEvent represents a comment on a merge event.
-//
-// GitLab API docs:
-// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#comment-on-merge-request
-type MergeCommentEvent struct {
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#comment-on-merge-request
+type MergeCommentEvent struct {
 	ObjectKind string `json:"object_kind"`
 	User       *User  `json:"user"`
 	ProjectID  int    `json:"project_id"`
@@ -411,118 +460,6 @@ type MergeCommentEvent struct {
 	} `json:"merge_request"`
 }
 
-// IssueCommentEvent represents a comment on an issue event.
-//
-// GitLab API docs:
-// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#comment-on-issue
-type IssueCommentEvent struct {
-	ObjectKind string `json:"object_kind"`
-	User       *User  `json:"user"`
-	ProjectID  int    `json:"project_id"`
-	Project    struct {
-		Name              string          `json:"name"`
-		Description       string          `json:"description"`
-		AvatarURL         string          `json:"avatar_url"`
-		GitSSHURL         string          `json:"git_ssh_url"`
-		GitHTTPURL        string          `json:"git_http_url"`
-		Namespace         string          `json:"namespace"`
-		PathWithNamespace string          `json:"path_with_namespace"`
-		DefaultBranch     string          `json:"default_branch"`
-		Homepage          string          `json:"homepage"`
-		URL               string          `json:"url"`
-		SSHURL            string          `json:"ssh_url"`
-		HTTPURL           string          `json:"http_url"`
-		WebURL            string          `json:"web_url"`
-		Visibility        VisibilityValue `json:"visibility"`
-	} `json:"project"`
-	Repository       *Repository `json:"repository"`
-	ObjectAttributes struct {
-		ID           int     `json:"id"`
-		Note         string  `json:"note"`
-		NoteableType string  `json:"noteable_type"`
-		AuthorID     int     `json:"author_id"`
-		CreatedAt    string  `json:"created_at"`
-		UpdatedAt    string  `json:"updated_at"`
-		ProjectID    int     `json:"project_id"`
-		Attachment   string  `json:"attachment"`
-		LineCode     string  `json:"line_code"`
-		CommitID     string  `json:"commit_id"`
-		NoteableID   int     `json:"noteable_id"`
-		System       bool    `json:"system"`
-		StDiff       []*Diff `json:"st_diff"`
-		URL          string  `json:"url"`
-	} `json:"object_attributes"`
-	Issue struct {
-		ID                  int      `json:"id"`
-		IID                 int      `json:"iid"`
-		ProjectID           int      `json:"project_id"`
-		MilestoneID         int      `json:"milestone_id"`
-		AuthorID            int      `json:"author_id"`
-		Description         string   `json:"description"`
-		State               string   `json:"state"`
-		Title               string   `json:"title"`
-		LastEditedAt        string   `json:"last_edit_at"`
-		LastEditedByID      int      `json:"last_edited_by_id"`
-		UpdatedAt           string   `json:"updated_at"`
-		UpdatedByID         int      `json:"updated_by_id"`
-		CreatedAt           string   `json:"created_at"`
-		ClosedAt            string   `json:"closed_at"`
-		DueDate             *ISOTime `json:"due_date"`
-		URL                 string   `json:"url"`
-		TimeEstimate        int      `json:"time_estimate"`
-		Confidential        bool     `json:"confidential"`
-		TotalTimeSpent      int      `json:"total_time_spent"`
-		HumanTotalTimeSpent string   `json:"human_total_time_spent"`
-		HumanTimeEstimate   string   `json:"human_time_estimate"`
-		AssigneeIDs         []int    `json:"assignee_ids"`
-		AssigneeID          int      `json:"assignee_id"`
-	} `json:"issue"`
-}
-
-// SnippetCommentEvent represents a comment on a snippet event.
-//
-// GitLab API docs:
-// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#comment-on-code-snippet
-type SnippetCommentEvent struct {
-	ObjectKind string `json:"object_kind"`
-	User       *User  `json:"user"`
-	ProjectID  int    `json:"project_id"`
-	Project    struct {
-		Name              string          `json:"name"`
-		Description       string          `json:"description"`
-		AvatarURL         string          `json:"avatar_url"`
-		GitSSHURL         string          `json:"git_ssh_url"`
-		GitHTTPURL        string          `json:"git_http_url"`
-		Namespace         string          `json:"namespace"`
-		PathWithNamespace string          `json:"path_with_namespace"`
-		DefaultBranch     string          `json:"default_branch"`
-		Homepage          string          `json:"homepage"`
-		URL               string          `json:"url"`
-		SSHURL            string          `json:"ssh_url"`
-		HTTPURL           string          `json:"http_url"`
-		WebURL            string          `json:"web_url"`
-		Visibility        VisibilityValue `json:"visibility"`
-	} `json:"project"`
-	Repository       *Repository `json:"repository"`
-	ObjectAttributes struct {
-		ID           int    `json:"id"`
-		Note         string `json:"note"`
-		NoteableType string `json:"noteable_type"`
-		AuthorID     int    `json:"author_id"`
-		CreatedAt    string `json:"created_at"`
-		UpdatedAt    string `json:"updated_at"`
-		ProjectID    int    `json:"project_id"`
-		Attachment   string `json:"attachment"`
-		LineCode     string `json:"line_code"`
-		CommitID     string `json:"commit_id"`
-		NoteableID   int    `json:"noteable_id"`
-		System       bool   `json:"system"`
-		StDiff       *Diff  `json:"st_diff"`
-		URL          string `json:"url"`
-	} `json:"object_attributes"`
-	Snippet *Snippet `json:"snippet"`
-}
-
 // MergeEvent represents a merge event.
 //
 // GitLab API docs:
@@ -695,47 +632,6 @@ func (p *MergeParams) UnmarshalJSON(b []byte) error {
 	return nil
 }
 
-// WikiPageEvent represents a wiki page event.
-//
-// GitLab API docs:
-// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#wiki-page-events
-type WikiPageEvent struct {
-	ObjectKind string `json:"object_kind"`
-	User       *User  `json:"user"`
-	Project    struct {
-		Name              string          `json:"name"`
-		Description       string          `json:"description"`
-		AvatarURL         string          `json:"avatar_url"`
-		GitSSHURL         string          `json:"git_ssh_url"`
-		GitHTTPURL        string          `json:"git_http_url"`
-		Namespace         string          `json:"namespace"`
-		PathWithNamespace string          `json:"path_with_namespace"`
-		DefaultBranch     string          `json:"default_branch"`
-		Homepage          string          `json:"homepage"`
-		URL               string          `json:"url"`
-		SSHURL            string          `json:"ssh_url"`
-		HTTPURL           string          `json:"http_url"`
-		WebURL            string          `json:"web_url"`
-		Visibility        VisibilityValue `json:"visibility"`
-	} `json:"project"`
-	Wiki struct {
-		WebURL            string `json:"web_url"`
-		GitSSHURL         string `json:"git_ssh_url"`
-		GitHTTPURL        string `json:"git_http_url"`
-		PathWithNamespace string `json:"path_with_namespace"`
-		DefaultBranch     string `json:"default_branch"`
-	} `json:"wiki"`
-	ObjectAttributes struct {
-		Title   string `json:"title"`
-		Content string `json:"content"`
-		Format  string `json:"format"`
-		Message string `json:"message"`
-		Slug    string `json:"slug"`
-		URL     string `json:"url"`
-		Action  string `json:"action"`
-	} `json:"object_attributes"`
-}
-
 // PipelineEvent represents a pipeline event.
 //
 // GitLab API docs:
@@ -827,56 +723,68 @@ type PipelineEvent struct {
 	} `json:"builds"`
 }
 
-//BuildEvent represents a build event
+// PushEvent represents a push event.
 //
 // GitLab API docs:
-// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#build-events
-type BuildEvent struct {
-	ObjectKind        string  `json:"object_kind"`
-	Ref               string  `json:"ref"`
-	Tag               bool    `json:"tag"`
-	BeforeSHA         string  `json:"before_sha"`
-	SHA               string  `json:"sha"`
-	BuildID           int     `json:"build_id"`
-	BuildName         string  `json:"build_name"`
-	BuildStage        string  `json:"build_stage"`
-	BuildStatus       string  `json:"build_status"`
-	BuildStartedAt    string  `json:"build_started_at"`
-	BuildFinishedAt   string  `json:"build_finished_at"`
-	BuildDuration     float64 `json:"build_duration"`
-	BuildAllowFailure bool    `json:"build_allow_failure"`
-	ProjectID         int     `json:"project_id"`
-	ProjectName       string  `json:"project_name"`
-	User              struct {
-		ID    int    `json:"id"`
-		Name  string `json:"name"`
-		Email string `json:"email"`
-	} `json:"user"`
-	Commit struct {
-		ID          int    `json:"id"`
-		SHA         string `json:"sha"`
-		Message     string `json:"message"`
-		AuthorName  string `json:"author_name"`
-		AuthorEmail string `json:"author_email"`
-		Status      string `json:"status"`
-		Duration    int    `json:"duration"`
-		StartedAt   string `json:"started_at"`
-		FinishedAt  string `json:"finished_at"`
-	} `json:"commit"`
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#push-events
+type PushEvent struct {
+	ObjectKind   string `json:"object_kind"`
+	Before       string `json:"before"`
+	After        string `json:"after"`
+	Ref          string `json:"ref"`
+	CheckoutSHA  string `json:"checkout_sha"`
+	UserID       int    `json:"user_id"`
+	UserName     string `json:"user_name"`
+	UserUsername string `json:"user_username"`
+	UserEmail    string `json:"user_email"`
+	UserAvatar   string `json:"user_avatar"`
+	ProjectID    int    `json:"project_id"`
+	Project      struct {
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
 	Repository *Repository `json:"repository"`
+	Commits    []*struct {
+		ID        string     `json:"id"`
+		Message   string     `json:"message"`
+		Timestamp *time.Time `json:"timestamp"`
+		URL       string     `json:"url"`
+		Author    struct {
+			Name  string `json:"name"`
+			Email string `json:"email"`
+		} `json:"author"`
+		Added    []string `json:"added"`
+		Modified []string `json:"modified"`
+		Removed  []string `json:"removed"`
+	} `json:"commits"`
+	TotalCommitsCount int `json:"total_commits_count"`
 }
 
-// DeploymentEvent represents a deployment event
+// ReleaseEvent represents a release event
 //
 // GitLab API docs:
-// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#deployment-events
-type DeploymentEvent struct {
-	ObjectKind    string `json:"object_kind"`
-	Status        string `json:"status"`
-	DeployableID  int    `json:"deployable_id"`
-	DeployableURL string `json:"deployable_url"`
-	Environment   string `json:"environment"`
-	Project       struct {
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#release-events
+type ReleaseEvent struct {
+	ID          int    `json:"id"`
+	CreatedAt   string `json:"created_at"` // Should be *time.Time (see Gitlab issue #21468)
+	Description string `json:"description"`
+	Name        string `json:"name"`
+	Tag         string `json:"tag"`
+	ReleasedAt  string `json:"released_at"` // Should be *time.Time (see Gitlab issue #21468)
+	ObjectKind  string `json:"object_kind"`
+	Project     struct {
 		ID                int     `json:"id"`
 		Name              string  `json:"name"`
 		Description       string  `json:"description"`
@@ -894,14 +802,165 @@ type DeploymentEvent struct {
 		SSHURL            string  `json:"ssh_url"`
 		HTTPURL           string  `json:"http_url"`
 	} `json:"project"`
-	ShortSHA string `json:"short_sha"`
-	User     struct {
-		Name      string `json:"name"`
-		Username  string `json:"username"`
-		AvatarURL string `json:"avatar_url"`
-		Email     string `json:"email"`
-	} `json:"user"`
-	UserURL     string `json:"user_url"`
-	CommitURL   string `json:"commit_url"`
-	CommitTitle string `json:"commit_title"`
+	URL    string `json:"url"`
+	Action string `json:"action"`
+	Assets struct {
+		Count int `json:"count"`
+		Links []struct {
+			ID       int    `json:"id"`
+			External bool   `json:"external"`
+			LinkType string `json:"link_type"`
+			Name     string `json:"name"`
+			URL      string `json:"url"`
+		} `json:"links"`
+		Sources []struct {
+			Format string `json:"format"`
+			URL    string `json:"url"`
+		} `json:"sources"`
+	} `json:"assets"`
+	Commit struct {
+		ID        string `json:"id"`
+		Message   string `json:"message"`
+		Title     string `json:"title"`
+		Timestamp string `json:"timestamp"` // Should be *time.Time (see Gitlab issue #21468)
+		URL       string `json:"url"`
+		Author    struct {
+			Name  string `json:"name"`
+			Email string `json:"email"`
+		} `json:"author"`
+	} `json:"commit"`
+}
+
+// SnippetCommentEvent represents a comment on a snippet event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#comment-on-code-snippet
+type SnippetCommentEvent struct {
+	ObjectKind string `json:"object_kind"`
+	User       *User  `json:"user"`
+	ProjectID  int    `json:"project_id"`
+	Project    struct {
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	Repository       *Repository `json:"repository"`
+	ObjectAttributes struct {
+		ID           int    `json:"id"`
+		Note         string `json:"note"`
+		NoteableType string `json:"noteable_type"`
+		AuthorID     int    `json:"author_id"`
+		CreatedAt    string `json:"created_at"`
+		UpdatedAt    string `json:"updated_at"`
+		ProjectID    int    `json:"project_id"`
+		Attachment   string `json:"attachment"`
+		LineCode     string `json:"line_code"`
+		CommitID     string `json:"commit_id"`
+		NoteableID   int    `json:"noteable_id"`
+		System       bool   `json:"system"`
+		StDiff       *Diff  `json:"st_diff"`
+		URL          string `json:"url"`
+	} `json:"object_attributes"`
+	Snippet *Snippet `json:"snippet"`
+}
+
+// TagEvent represents a tag event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#tag-events
+type TagEvent struct {
+	ObjectKind  string `json:"object_kind"`
+	Before      string `json:"before"`
+	After       string `json:"after"`
+	Ref         string `json:"ref"`
+	CheckoutSHA string `json:"checkout_sha"`
+	UserID      int    `json:"user_id"`
+	UserName    string `json:"user_name"`
+	UserAvatar  string `json:"user_avatar"`
+	UserEmail   string `json:"user_email"`
+	ProjectID   int    `json:"project_id"`
+	Message     string `json:"message"`
+	Project     struct {
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	Repository *Repository `json:"repository"`
+	Commits    []*struct {
+		ID        string     `json:"id"`
+		Message   string     `json:"message"`
+		Timestamp *time.Time `json:"timestamp"`
+		URL       string     `json:"url"`
+		Author    struct {
+			Name  string `json:"name"`
+			Email string `json:"email"`
+		} `json:"author"`
+		Added    []string `json:"added"`
+		Modified []string `json:"modified"`
+		Removed  []string `json:"removed"`
+	} `json:"commits"`
+	TotalCommitsCount int `json:"total_commits_count"`
+}
+
+// WikiPageEvent represents a wiki page event.
+//
+// GitLab API docs:
+// https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#wiki-page-events
+type WikiPageEvent struct {
+	ObjectKind string `json:"object_kind"`
+	User       *User  `json:"user"`
+	Project    struct {
+		Name              string          `json:"name"`
+		Description       string          `json:"description"`
+		AvatarURL         string          `json:"avatar_url"`
+		GitSSHURL         string          `json:"git_ssh_url"`
+		GitHTTPURL        string          `json:"git_http_url"`
+		Namespace         string          `json:"namespace"`
+		PathWithNamespace string          `json:"path_with_namespace"`
+		DefaultBranch     string          `json:"default_branch"`
+		Homepage          string          `json:"homepage"`
+		URL               string          `json:"url"`
+		SSHURL            string          `json:"ssh_url"`
+		HTTPURL           string          `json:"http_url"`
+		WebURL            string          `json:"web_url"`
+		Visibility        VisibilityValue `json:"visibility"`
+	} `json:"project"`
+	Wiki struct {
+		WebURL            string `json:"web_url"`
+		GitSSHURL         string `json:"git_ssh_url"`
+		GitHTTPURL        string `json:"git_http_url"`
+		PathWithNamespace string `json:"path_with_namespace"`
+		DefaultBranch     string `json:"default_branch"`
+	} `json:"wiki"`
+	ObjectAttributes struct {
+		Title   string `json:"title"`
+		Content string `json:"content"`
+		Format  string `json:"format"`
+		Message string `json:"message"`
+		Slug    string `json:"slug"`
+		URL     string `json:"url"`
+		Action  string `json:"action"`
+	} `json:"object_attributes"`
 }
diff --git a/group_hooks.go b/group_hooks.go
index dbc8408..2f03c71 100644
--- a/group_hooks.go
+++ b/group_hooks.go
@@ -40,6 +40,7 @@ type GroupHook struct {
 	PipelineEvents           bool       `json:"pipeline_events"`
 	WikiPageEvents           bool       `json:"wiki_page_events"`
 	DeploymentEvents         bool       `json:"deployment_events"`
+	ReleasesEvents           bool       `json:"releases_events"`
 	EnableSSLVerification    bool       `json:"enable_ssl_verification"`
 	CreatedAt                *time.Time `json:"created_at"`
 }
@@ -108,6 +109,7 @@ type AddGroupHookOptions struct {
 	PipelineEvents           *bool   `url:"pipeline_events,omitempty"  json:"pipeline_events,omitempty"`
 	WikiPageEvents           *bool   `url:"wiki_page_events,omitempty"  json:"wiki_page_events,omitempty"`
 	DeploymentEvents         *bool   `url:"deployment_events,omitempty" json:"deployment_events,omitempty"`
+	ReleasesEvents           *bool   `url:"releases_events,omitempty" json:"releases_events,omitempty"`
 	EnableSSLVerification    *bool   `url:"enable_ssl_verification,omitempty"  json:"enable_ssl_verification,omitempty"`
 	Token                    *string `url:"token,omitempty" json:"token,omitempty"`
 }
@@ -153,6 +155,7 @@ type EditGroupHookOptions struct {
 	PipelineEvents           *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
 	WikiPageEvents           *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
 	DeploymentEvents         *bool   `url:"deployment_events,omitempty" json:"deployment_events,omitempty"`
+	ReleasesEvents           *bool   `url:"releases_events,omitempty" json:"releases_events,omitempty"`
 	EnableSSLVerification    *bool   `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
 	Token                    *string `url:"token,omitempty" json:"token,omitempty"`
 }
diff --git a/projects.go b/projects.go
index cc1282b..20761d4 100644
--- a/projects.go
+++ b/projects.go
@@ -948,6 +948,7 @@ type ProjectHook struct {
 	PipelineEvents           bool       `json:"pipeline_events"`
 	WikiPageEvents           bool       `json:"wiki_page_events"`
 	DeploymentEvents         bool       `json:"deployment_events"`
+	ReleasesEvents           bool       `json:"releases_events"`
 	EnableSSLVerification    bool       `json:"enable_ssl_verification"`
 	CreatedAt                *time.Time `json:"created_at"`
 }
@@ -1025,6 +1026,7 @@ type AddProjectHookOptions struct {
 	PipelineEvents           *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
 	WikiPageEvents           *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
 	DeploymentEvents         *bool   `url:"deployment_events,omitempty" json:"deployment_events,omitempty"`
+	ReleasesEvents           *bool   `url:"releases_events,omitempty" json:"releases_events,omitempty"`
 	EnableSSLVerification    *bool   `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
 	Token                    *string `url:"token,omitempty" json:"token,omitempty"`
 }
@@ -1072,6 +1074,7 @@ type EditProjectHookOptions struct {
 	PipelineEvents           *bool   `url:"pipeline_events,omitempty" json:"pipeline_events,omitempty"`
 	WikiPageEvents           *bool   `url:"wiki_page_events,omitempty" json:"wiki_page_events,omitempty"`
 	DeploymentEvents         *bool   `url:"deployment_events,omitempty" json:"deployment_events,omitempty"`
+	ReleasesEvents           *bool   `url:"releases_events,omitempty" json:"releases_events,omitempty"`
 	EnableSSLVerification    *bool   `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"`
 	Token                    *string `url:"token,omitempty" json:"token,omitempty"`
 }


Test patch:
diff --git a/event_parsing_webhook_test.go b/event_parsing_webhook_test.go
index d87d000..ffadbd7 100644
--- a/event_parsing_webhook_test.go
+++ b/event_parsing_webhook_test.go
@@ -36,67 +36,105 @@ func TestWebhookEventType(t *testing.T) {
 	}
 }
 
-func TestParsePushHook(t *testing.T) {
-	raw := loadFixture("testdata/webhooks/push.json")
+func TestParseBuildHook(t *testing.T) {
+	raw := loadFixture("testdata/webhooks/build.json")
 
-	parsedEvent, err := ParseWebhook("Push Hook", raw)
+	parsedEvent, err := ParseWebhook("Build Hook", raw)
 	if err != nil {
-		t.Errorf("Error parsing push hook: %s", err)
+		t.Errorf("Error parsing build hook: %s", err)
 	}
 
-	event, ok := parsedEvent.(*PushEvent)
+	event, ok := parsedEvent.(*BuildEvent)
 	if !ok {
-		t.Errorf("Expected PushEvent, but parsing produced %T", parsedEvent)
+		t.Errorf("Expected BuildEvent, but parsing produced %T", parsedEvent)
 	}
 
-	if event.ObjectKind != "push" {
-		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "push")
+	if event.ObjectKind != "build" {
+		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "build")
 	}
 
-	if event.ProjectID != 15 {
-		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 15)
+	if event.BuildID != 1977 {
+		t.Errorf("BuildID is %v, want %v", event.BuildID, 1977)
 	}
 
-	if event.UserName != exampleEventUserName {
-		t.Errorf("Username is %s, want %s", event.UserName, exampleEventUserName)
+	if event.BuildAllowFailure {
+		t.Errorf("BuildAllowFailure is %v, want %v", event.BuildAllowFailure, false)
 	}
 
-	if event.Commits[0] == nil || event.Commits[0].Timestamp == nil {
-		t.Errorf("Commit Timestamp isn't nil")
+	if event.Commit.SHA != "2293ada6b400935a1378653304eaf6221e0fdb8f" {
+		t.Errorf("Commit SHA is %v, want %v", event.Commit.SHA, "2293ada6b400935a1378653304eaf6221e0fdb8f")
 	}
+}
 
-	if event.Commits[0] == nil || event.Commits[0].Author.Name != "Jordi Mallach" {
-		t.Errorf("Commit Username is %s, want %s", event.UserName, "Jordi Mallach")
+func TestParseCommitCommentHook(t *testing.T) {
+	raw := loadFixture("testdata/webhooks/note_commit.json")
+
+	parsedEvent, err := ParseWebhook("Note Hook", raw)
+	if err != nil {
+		t.Errorf("Error parsing note hook: %s", err)
+	}
+
+	event, ok := parsedEvent.(*CommitCommentEvent)
+	if !ok {
+		t.Errorf("Expected CommitCommentEvent, but parsing produced %T", parsedEvent)
+	}
+
+	if event.ObjectKind != string(NoteEventTargetType) {
+		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, NoteEventTargetType)
+	}
+
+	if event.ProjectID != 5 {
+		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 5)
+	}
+
+	if event.ObjectAttributes.NoteableType != "Commit" {
+		t.Errorf("NoteableType is %v, want %v", event.ObjectAttributes.NoteableType, "Commit")
+	}
+
+	if event.Commit.ID != "cfe32cf61b73a0d5e9f13e774abde7ff789b1660" {
+		t.Errorf("CommitID is %v, want %v", event.Commit.ID, "cfe32cf61b73a0d5e9f13e774abde7ff789b1660")
 	}
 }
 
-func TestParseTagHook(t *testing.T) {
-	raw := loadFixture("testdata/webhooks/tag_push.json")
+func TestParseHookWebHook(t *testing.T) {
+	parsedEvent1, err := ParseHook("Merge Request Hook", loadFixture("testdata/webhooks/merge_request.json"))
+	if err != nil {
+		t.Errorf("Error parsing build hook: %s", err)
+	}
+	parsedEvent2, err := ParseWebhook("Merge Request Hook", loadFixture("testdata/webhooks/merge_request.json"))
+	if err != nil {
+		t.Errorf("Error parsing build hook: %s", err)
+	}
+	assert.Equal(t, parsedEvent1, parsedEvent2)
+}
 
-	parsedEvent, err := ParseWebhook("Tag Push Hook", raw)
+func TestParseIssueCommentHook(t *testing.T) {
+	raw := loadFixture("testdata/webhooks/note_issue.json")
+
+	parsedEvent, err := ParseWebhook("Note Hook", raw)
 	if err != nil {
-		t.Errorf("Error parsing tag hook: %s", err)
+		t.Errorf("Error parsing note hook: %s", err)
 	}
 
-	event, ok := parsedEvent.(*TagEvent)
+	event, ok := parsedEvent.(*IssueCommentEvent)
 	if !ok {
-		t.Errorf("Expected TagEvent, but parsing produced %T", parsedEvent)
+		t.Errorf("Expected IssueCommentEvent, but parsing produced %T", parsedEvent)
 	}
 
-	if event.ObjectKind != "tag_push" {
-		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "tag_push")
+	if event.ObjectKind != string(NoteEventTargetType) {
+		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, NoteEventTargetType)
 	}
 
-	if event.ProjectID != 1 {
-		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 1)
+	if event.ProjectID != 5 {
+		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 5)
 	}
 
-	if event.UserName != exampleEventUserName {
-		t.Errorf("Username is %s, want %s", event.UserName, exampleEventUserName)
+	if event.ObjectAttributes.NoteableType != "Issue" {
+		t.Errorf("NoteableType is %v, want %v", event.ObjectAttributes.NoteableType, "Issue")
 	}
 
-	if event.Ref != "refs/tags/v1.0.0" {
-		t.Errorf("Ref is %s, want %s", event.Ref, "refs/tags/v1.0.0")
+	if event.Issue.Title != "test" {
+		t.Errorf("Issue title is %v, want %v", event.Issue.Title, "test")
 	}
 }
 
@@ -139,259 +177,243 @@ func TestParseIssueHook(t *testing.T) {
 	assert.Equal(t, "New title", event.Changes.Title.Current)
 }
 
-func TestParseCommitCommentHook(t *testing.T) {
-	raw := loadFixture("testdata/webhooks/note_commit.json")
+func TestParseMergeRequestCommentHook(t *testing.T) {
+	raw := loadFixture("testdata/webhooks/note_merge_request.json")
 
 	parsedEvent, err := ParseWebhook("Note Hook", raw)
 	if err != nil {
 		t.Errorf("Error parsing note hook: %s", err)
 	}
 
-	event, ok := parsedEvent.(*CommitCommentEvent)
+	event, ok := parsedEvent.(*MergeCommentEvent)
 	if !ok {
-		t.Errorf("Expected CommitCommentEvent, but parsing produced %T", parsedEvent)
+		t.Errorf("Expected MergeCommentEvent, but parsing produced %T", parsedEvent)
 	}
 
 	if event.ObjectKind != string(NoteEventTargetType) {
-		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, NoteEventTargetType)
+		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "note")
 	}
 
 	if event.ProjectID != 5 {
 		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 5)
 	}
 
-	if event.ObjectAttributes.NoteableType != "Commit" {
-		t.Errorf("NoteableType is %v, want %v", event.ObjectAttributes.NoteableType, "Commit")
+	if event.ObjectAttributes.NoteableType != "MergeRequest" {
+		t.Errorf("NoteableType is %v, want %v", event.ObjectAttributes.NoteableType, "MergeRequest")
 	}
 
-	if event.Commit.ID != "cfe32cf61b73a0d5e9f13e774abde7ff789b1660" {
-		t.Errorf("CommitID is %v, want %v", event.Commit.ID, "cfe32cf61b73a0d5e9f13e774abde7ff789b1660")
+	if event.MergeRequest.ID != 7 {
+		t.Errorf("MergeRequest ID is %v, want %v", event.MergeRequest.ID, 7)
 	}
 }
 
-func TestParseMergeRequestCommentHook(t *testing.T) {
-	raw := loadFixture("testdata/webhooks/note_merge_request.json")
+func TestParseMergeRequestHook(t *testing.T) {
+	raw := loadFixture("testdata/webhooks/merge_request.json")
 
-	parsedEvent, err := ParseWebhook("Note Hook", raw)
+	parsedEvent, err := ParseWebhook("Merge Request Hook", raw)
 	if err != nil {
-		t.Errorf("Error parsing note hook: %s", err)
+		t.Errorf("Error parsing merge request hook: %s", err)
 	}
 
-	event, ok := parsedEvent.(*MergeCommentEvent)
+	event, ok := parsedEvent.(*MergeEvent)
 	if !ok {
-		t.Errorf("Expected MergeCommentEvent, but parsing produced %T", parsedEvent)
+		t.Errorf("Expected MergeEvent, but parsing produced %T", parsedEvent)
 	}
 
-	if event.ObjectKind != string(NoteEventTargetType) {
-		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "note")
+	if event.ObjectKind != "merge_request" {
+		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "merge_request")
 	}
 
-	if event.ProjectID != 5 {
-		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 5)
+	if event.ObjectAttributes.MergeStatus != "unchecked" {
+		t.Errorf("MergeStatus is %v, want %v", event.ObjectAttributes.MergeStatus, "unchecked")
 	}
 
-	if event.ObjectAttributes.NoteableType != "MergeRequest" {
-		t.Errorf("NoteableType is %v, want %v", event.ObjectAttributes.NoteableType, "MergeRequest")
+	if event.ObjectAttributes.LastCommit.ID != "da1560886d4f094c3e6c9ef40349f7d38b5d27d7" {
+		t.Errorf("LastCommit ID is %v, want %v", event.ObjectAttributes.LastCommit.ID, "da1560886d4f094c3e6c9ef40349f7d38b5d27d7")
 	}
 
-	if event.MergeRequest.ID != 7 {
-		t.Errorf("MergeRequest ID is %v, want %v", event.MergeRequest.ID, 7)
+	if event.ObjectAttributes.WorkInProgress {
+		t.Errorf("WorkInProgress is %v, want %v", event.ObjectAttributes.WorkInProgress, false)
 	}
+	assert.Equal(t, 1, len(event.Labels))
+	assert.Equal(t, 0, event.Changes.UpdatedByID.Previous)
+	assert.Equal(t, 1, event.Changes.UpdatedByID.Current)
+	assert.Equal(t, 1, len(event.Changes.Labels.Previous))
+	assert.Equal(t, 1, len(event.Changes.Labels.Current))
 }
 
-func TestParseIssueCommentHook(t *testing.T) {
-	raw := loadFixture("testdata/webhooks/note_issue.json")
+func TestParsePipelineHook(t *testing.T) {
+	raw := loadFixture("testdata/webhooks/pipeline.json")
 
-	parsedEvent, err := ParseWebhook("Note Hook", raw)
+	parsedEvent, err := ParseWebhook("Pipeline Hook", raw)
 	if err != nil {
-		t.Errorf("Error parsing note hook: %s", err)
+		t.Errorf("Error parsing pipeline hook: %s", err)
 	}
 
-	event, ok := parsedEvent.(*IssueCommentEvent)
+	event, ok := parsedEvent.(*PipelineEvent)
 	if !ok {
-		t.Errorf("Expected IssueCommentEvent, but parsing produced %T", parsedEvent)
+		t.Errorf("Expected PipelineEvent, but parsing produced %T", parsedEvent)
 	}
 
-	if event.ObjectKind != string(NoteEventTargetType) {
-		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, NoteEventTargetType)
+	if event.ObjectKind != "pipeline" {
+		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "pipeline")
 	}
 
-	if event.ProjectID != 5 {
-		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 5)
+	if event.ObjectAttributes.Duration != 63 {
+		t.Errorf("Duration is %v, want %v", event.ObjectAttributes.Duration, 63)
 	}
 
-	if event.ObjectAttributes.NoteableType != "Issue" {
-		t.Errorf("NoteableType is %v, want %v", event.ObjectAttributes.NoteableType, "Issue")
+	if event.Commit.ID != "bcbb5ec396a2c0f828686f14fac9b80b780504f2" {
+		t.Errorf("Commit ID is %v, want %v", event.Commit.ID, "bcbb5ec396a2c0f828686f14fac9b80b780504f2")
 	}
 
-	if event.Issue.Title != "test" {
-		t.Errorf("Issue title is %v, want %v", event.Issue.Title, "test")
+	if event.Builds[0].ID != 380 {
+		t.Errorf("Builds[0] ID is %v, want %v", event.Builds[0].ID, 380)
 	}
 }
 
-func TestParseSnippetCommentHook(t *testing.T) {
-	raw := loadFixture("testdata/webhooks/note_snippet.json")
+func TestParsePushHook(t *testing.T) {
+	raw := loadFixture("testdata/webhooks/push.json")
 
-	parsedEvent, err := ParseWebhook("Note Hook", raw)
+	parsedEvent, err := ParseWebhook("Push Hook", raw)
 	if err != nil {
-		t.Errorf("Error parsing note hook: %s", err)
+		t.Errorf("Error parsing push hook: %s", err)
 	}
 
-	event, ok := parsedEvent.(*SnippetCommentEvent)
+	event, ok := parsedEvent.(*PushEvent)
 	if !ok {
-		t.Errorf("Expected SnippetCommentEvent, but parsing produced %T", parsedEvent)
+		t.Errorf("Expected PushEvent, but parsing produced %T", parsedEvent)
 	}
 
-	if event.ObjectKind != string(NoteEventTargetType) {
-		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, NoteEventTargetType)
+	if event.ObjectKind != "push" {
+		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "push")
 	}
 
-	if event.ProjectID != 5 {
-		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 5)
+	if event.ProjectID != 15 {
+		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 15)
 	}
 
-	if event.ObjectAttributes.NoteableType != "Snippet" {
-		t.Errorf("NoteableType is %v, want %v", event.ObjectAttributes.NoteableType, "Snippet")
+	if event.UserName != exampleEventUserName {
+		t.Errorf("Username is %s, want %s", event.UserName, exampleEventUserName)
 	}
 
-	if event.Snippet.Title != "test" {
-		t.Errorf("Snippet title is %v, want %v", event.Snippet.Title, "test")
+	if event.Commits[0] == nil || event.Commits[0].Timestamp == nil {
+		t.Errorf("Commit Timestamp isn't nil")
+	}
+
+	if event.Commits[0] == nil || event.Commits[0].Author.Name != "Jordi Mallach" {
+		t.Errorf("Commit Username is %s, want %s", event.UserName, "Jordi Mallach")
 	}
 }
 
-func TestParseMergeRequestHook(t *testing.T) {
-	raw := loadFixture("testdata/webhooks/merge_request.json")
+func TestParseReleaseHook(t *testing.T) {
+	raw := loadFixture("testdata/webhooks/release.json")
 
-	parsedEvent, err := ParseWebhook("Merge Request Hook", raw)
+	parsedEvent, err := ParseWebhook("Release Hook", raw)
 	if err != nil {
-		t.Errorf("Error parsing merge request hook: %s", err)
+		t.Errorf("Error parsing release hook: %s", err)
 	}
 
-	event, ok := parsedEvent.(*MergeEvent)
+	event, ok := parsedEvent.(*ReleaseEvent)
 	if !ok {
-		t.Errorf("Expected MergeEvent, but parsing produced %T", parsedEvent)
+		t.Errorf("Expected ReleaseEvent, but parsing produced %T", parsedEvent)
 	}
 
-	if event.ObjectKind != "merge_request" {
-		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "merge_request")
-	}
-
-	if event.ObjectAttributes.MergeStatus != "unchecked" {
-		t.Errorf("MergeStatus is %v, want %v", event.ObjectAttributes.MergeStatus, "unchecked")
+	if event.ObjectKind != "release" {
+		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "release")
 	}
 
-	if event.ObjectAttributes.LastCommit.ID != "da1560886d4f094c3e6c9ef40349f7d38b5d27d7" {
-		t.Errorf("LastCommit ID is %v, want %v", event.ObjectAttributes.LastCommit.ID, "da1560886d4f094c3e6c9ef40349f7d38b5d27d7")
+	if event.Project.Name != "Project Name" {
+		t.Errorf("Project name is %v, want %v", event.Project.Name, "Project Name")
 	}
-
-	if event.ObjectAttributes.WorkInProgress {
-		t.Errorf("WorkInProgress is %v, want %v", event.ObjectAttributes.WorkInProgress, false)
-	}
-	assert.Equal(t, 1, len(event.Labels))
-	assert.Equal(t, 0, event.Changes.UpdatedByID.Previous)
-	assert.Equal(t, 1, event.Changes.UpdatedByID.Current)
-	assert.Equal(t, 1, len(event.Changes.Labels.Previous))
-	assert.Equal(t, 1, len(event.Changes.Labels.Current))
 }
 
-func TestParseWikiPageHook(t *testing.T) {
-	raw := loadFixture("testdata/webhooks/wiki_page.json")
+func TestParseSnippetCommentHook(t *testing.T) {
+	raw := loadFixture("testdata/webhooks/note_snippet.json")
 
-	parsedEvent, err := ParseWebhook("Wiki Page Hook", raw)
+	parsedEvent, err := ParseWebhook("Note Hook", raw)
 	if err != nil {
-		t.Errorf("Error parsing wiki page hook: %s", err)
+		t.Errorf("Error parsing note hook: %s", err)
 	}
 
-	event, ok := parsedEvent.(*WikiPageEvent)
+	event, ok := parsedEvent.(*SnippetCommentEvent)
 	if !ok {
-		t.Errorf("Expected WikiPageEvent, but parsing produced %T", parsedEvent)
+		t.Errorf("Expected SnippetCommentEvent, but parsing produced %T", parsedEvent)
 	}
 
-	if event.ObjectKind != "wiki_page" {
-		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "wiki_page")
+	if event.ObjectKind != string(NoteEventTargetType) {
+		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, NoteEventTargetType)
 	}
 
-	if event.Project.Name != "awesome-project" {
-		t.Errorf("Project name is %v, want %v", event.Project.Name, "awesome-project")
+	if event.ProjectID != 5 {
+		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 5)
 	}
 
-	if event.Wiki.WebURL != "http://example.com/root/awesome-project/wikis/home" {
-		t.Errorf("Wiki web URL is %v, want %v", event.Wiki.WebURL, "http://example.com/root/awesome-project/wikis/home")
+	if event.ObjectAttributes.NoteableType != "Snippet" {
+		t.Errorf("NoteableType is %v, want %v", event.ObjectAttributes.NoteableType, "Snippet")
 	}
 
-	if event.ObjectAttributes.Message != "adding an awesome page to the wiki" {
-		t.Errorf("Message is %v, want %v", event.ObjectAttributes.Message, "adding an awesome page to the wiki")
+	if event.Snippet.Title != "test" {
+		t.Errorf("Snippet title is %v, want %v", event.Snippet.Title, "test")
 	}
 }
 
-func TestParsePipelineHook(t *testing.T) {
-	raw := loadFixture("testdata/webhooks/pipeline.json")
+func TestParseTagHook(t *testing.T) {
+	raw := loadFixture("testdata/webhooks/tag_push.json")
 
-	parsedEvent, err := ParseWebhook("Pipeline Hook", raw)
+	parsedEvent, err := ParseWebhook("Tag Push Hook", raw)
 	if err != nil {
-		t.Errorf("Error parsing pipeline hook: %s", err)
+		t.Errorf("Error parsing tag hook: %s", err)
 	}
 
-	event, ok := parsedEvent.(*PipelineEvent)
+	event, ok := parsedEvent.(*TagEvent)
 	if !ok {
-		t.Errorf("Expected PipelineEvent, but parsing produced %T", parsedEvent)
+		t.Errorf("Expected TagEvent, but parsing produced %T", parsedEvent)
 	}
 
-	if event.ObjectKind != "pipeline" {
-		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "pipeline")
+	if event.ObjectKind != "tag_push" {
+		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "tag_push")
 	}
 
-	if event.ObjectAttributes.Duration != 63 {
-		t.Errorf("Duration is %v, want %v", event.ObjectAttributes.Duration, 63)
+	if event.ProjectID != 1 {
+		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 1)
 	}
 
-	if event.Commit.ID != "bcbb5ec396a2c0f828686f14fac9b80b780504f2" {
-		t.Errorf("Commit ID is %v, want %v", event.Commit.ID, "bcbb5ec396a2c0f828686f14fac9b80b780504f2")
+	if event.UserName != exampleEventUserName {
+		t.Errorf("Username is %s, want %s", event.UserName, exampleEventUserName)
 	}
 
-	if event.Builds[0].ID != 380 {
-		t.Errorf("Builds[0] ID is %v, want %v", event.Builds[0].ID, 380)
+	if event.Ref != "refs/tags/v1.0.0" {
+		t.Errorf("Ref is %s, want %s", event.Ref, "refs/tags/v1.0.0")
 	}
 }
 
-func TestParseBuildHook(t *testing.T) {
-	raw := loadFixture("testdata/webhooks/build.json")
+func TestParseWikiPageHook(t *testing.T) {
+	raw := loadFixture("testdata/webhooks/wiki_page.json")
 
-	parsedEvent, err := ParseWebhook("Build Hook", raw)
+	parsedEvent, err := ParseWebhook("Wiki Page Hook", raw)
 	if err != nil {
-		t.Errorf("Error parsing build hook: %s", err)
+		t.Errorf("Error parsing wiki page hook: %s", err)
 	}
 
-	event, ok := parsedEvent.(*BuildEvent)
+	event, ok := parsedEvent.(*WikiPageEvent)
 	if !ok {
-		t.Errorf("Expected BuildEvent, but parsing produced %T", parsedEvent)
-	}
-
-	if event.ObjectKind != "build" {
-		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "build")
+		t.Errorf("Expected WikiPageEvent, but parsing produced %T", parsedEvent)
 	}
 
-	if event.BuildID != 1977 {
-		t.Errorf("BuildID is %v, want %v", event.BuildID, 1977)
+	if event.ObjectKind != "wiki_page" {
+		t.Errorf("ObjectKind is %v, want %v", event.ObjectKind, "wiki_page")
 	}
 
-	if event.BuildAllowFailure {
-		t.Errorf("BuildAllowFailure is %v, want %v", event.BuildAllowFailure, false)
+	if event.Project.Name != "awesome-project" {
+		t.Errorf("Project name is %v, want %v", event.Project.Name, "awesome-project")
 	}
 
-	if event.Commit.SHA != "2293ada6b400935a1378653304eaf6221e0fdb8f" {
-		t.Errorf("Commit SHA is %v, want %v", event.Commit.SHA, "2293ada6b400935a1378653304eaf6221e0fdb8f")
+	if event.Wiki.WebURL != "http://example.com/root/awesome-project/wikis/home" {
+		t.Errorf("Wiki web URL is %v, want %v", event.Wiki.WebURL, "http://example.com/root/awesome-project/wikis/home")
 	}
-}
 
-func TestParseHookWebHook(t *testing.T) {
-	parsedEvent1, err := ParseHook("Merge Request Hook", loadFixture("testdata/webhooks/merge_request.json"))
-	if err != nil {
-		t.Errorf("Error parsing build hook: %s", err)
-	}
-	parsedEvent2, err := ParseWebhook("Merge Request Hook", loadFixture("testdata/webhooks/merge_request.json"))
-	if err != nil {
-		t.Errorf("Error parsing build hook: %s", err)
+	if event.ObjectAttributes.Message != "adding an awesome page to the wiki" {
+		t.Errorf("Message is %v, want %v", event.ObjectAttributes.Message, "adding an awesome page to the wiki")
 	}
-	assert.Equal(t, parsedEvent1, parsedEvent2)
 }
diff --git a/event_webhook_types_test.go b/event_webhook_types_test.go
index ff17aea..244c315 100644
--- a/event_webhook_types_test.go
+++ b/event_webhook_types_test.go
@@ -21,33 +21,69 @@ import (
 	"testing"
 )
 
-func TestPushEventUnmarshal(t *testing.T) {
-	jsonObject := loadFixture("testdata/webhooks/push.json")
-	var event *PushEvent
+func TestBuildEventUnmarshal(t *testing.T) {
+	jsonObject := loadFixture("testdata/webhooks/build.json")
+
+	var event *BuildEvent
 	err := json.Unmarshal(jsonObject, &event)
 
 	if err != nil {
-		t.Errorf("Push Event can not unmarshaled: %v\n ", err.Error())
+		t.Errorf("Build Event can not unmarshaled: %v\n ", err.Error())
 	}
 
 	if event == nil {
-		t.Errorf("Push Event is null")
+		t.Errorf("Build Event is null")
 	}
 
-	if event.ProjectID != 15 {
-		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 15)
+	if event.BuildID != 1977 {
+		t.Errorf("BuildID is %v, want %v", event.BuildID, 1977)
 	}
+}
 
-	if event.UserName != exampleEventUserName {
-		t.Errorf("Username is %s, want %s", event.UserName, exampleEventUserName)
+func TestDeploymentEventUnmarshal(t *testing.T) {
+	jsonObject := loadFixture("testdata/webhooks/deployment.json")
+
+	var event *DeploymentEvent
+	err := json.Unmarshal(jsonObject, &event)
+
+	if err != nil {
+		t.Errorf("Deployment Event can not unmarshaled: %v\n ", err.Error())
 	}
 
-	if event.Commits[0] == nil || event.Commits[0].Timestamp == nil {
-		t.Errorf("Commit Timestamp isn't nil")
+	if event == nil {
+		t.Errorf("Deployment Event is null")
 	}
 
-	if event.Commits[0] == nil || event.Commits[0].Author.Name != "Jordi Mallach" {
-		t.Errorf("Commit Username is %s, want %s", event.UserName, "Jordi Mallach")
+	if event.Project.ID != 30 {
+		t.Errorf("Project.ID is %v, want %v", event.Project.ID, 30)
+	}
+
+	if event.User.Name == "" {
+		t.Errorf("Username is %s, want %s", event.User.Name, "Administrator")
+	}
+
+	if event.CommitTitle != "Add new file" {
+		t.Errorf("CommitTitle is %s, want %s", event.CommitTitle, "Add new file")
+	}
+}
+
+func TestIssueEventUnmarshal(t *testing.T) {
+	jsonObject := loadFixture("testdata/webhooks/issue.json")
+
+	var event *IssueEvent
+	err := json.Unmarshal(jsonObject, &event)
+
+	if err != nil {
+		t.Errorf("Deployment Event can not unmarshaled: %v\n ", err.Error())
+	}
+	if event.Project.ID != 1 {
+		t.Errorf("Project.ID is %v, want %v", event.Project.ID, 1)
+	}
+	if event.Changes.TotalTimeSpent.Previous != 8100 {
+		t.Errorf("Changes.TotalTimeSpent.Previous is %v , want %v", event.Changes.TotalTimeSpent.Previous, 8100)
+	}
+	if event.Changes.TotalTimeSpent.Current != 9900 {
+		t.Errorf("Changes.TotalTimeSpent.Current is %v , want %v", event.Changes.TotalTimeSpent.Current, 8100)
 	}
 }
 
@@ -97,56 +133,6 @@ func TestMergeEventUnmarshal(t *testing.T) {
 	}
 }
 
-func TestPipelineEventUnmarshal(t *testing.T) {
-	jsonObject := loadFixture("testdata/webhooks/pipeline.json")
-
-	var event *PipelineEvent
-	err := json.Unmarshal(jsonObject, &event)
-
-	if err != nil {
-		t.Errorf("Pipeline Event can not unmarshaled: %v\n ", err.Error())
-	}
-
-	if event == nil {
-		t.Errorf("Pipeline Event is null")
-	}
-
-	if event.ObjectAttributes.ID != 31 {
-		t.Errorf("ObjectAttributes is %v, want %v", event.ObjectAttributes.ID, 1977)
-	}
-
-	if event.User.Name == "" {
-		t.Errorf("Username is %s, want %s", event.User.Name, "Administrator")
-	}
-
-	if event.Commit.Timestamp == nil {
-		t.Errorf("Timestamp isn't nil")
-	}
-
-	if name := event.Commit.Author.Name; name != "User" {
-		t.Errorf("Commit Username is %s, want %s", name, "User")
-	}
-}
-
-func TestBuildEventUnmarshal(t *testing.T) {
-	jsonObject := loadFixture("testdata/webhooks/build.json")
-
-	var event *BuildEvent
-	err := json.Unmarshal(jsonObject, &event)
-
-	if err != nil {
-		t.Errorf("Build Event can not unmarshaled: %v\n ", err.Error())
-	}
-
-	if event == nil {
-		t.Errorf("Build Event is null")
-	}
-
-	if event.BuildID != 1977 {
-		t.Errorf("BuildID is %v, want %v", event.BuildID, 1977)
-	}
-}
-
 func TestMergeEventUnmarshalFromGroup(t *testing.T) {
 	jsonObject := loadFixture("testdata/webhooks/group_merge_request.json")
 
@@ -206,49 +192,106 @@ func TestMergeEventUnmarshalFromGroup(t *testing.T) {
 	}
 }
 
-func TestDeploymentEventUnmarshal(t *testing.T) {
-	jsonObject := loadFixture("testdata/webhooks/deployment.json")
+func TestPipelineEventUnmarshal(t *testing.T) {
+	jsonObject := loadFixture("testdata/webhooks/pipeline.json")
 
-	var event *DeploymentEvent
+	var event *PipelineEvent
 	err := json.Unmarshal(jsonObject, &event)
 
 	if err != nil {
-		t.Errorf("Deployment Event can not unmarshaled: %v\n ", err.Error())
+		t.Errorf("Pipeline Event can not unmarshaled: %v\n ", err.Error())
 	}
 
 	if event == nil {
-		t.Errorf("Deployment Event is null")
+		t.Errorf("Pipeline Event is null")
 	}
 
-	if event.Project.ID != 30 {
-		t.Errorf("Project.ID is %v, want %v", event.Project.ID, 30)
+	if event.ObjectAttributes.ID != 31 {
+		t.Errorf("ObjectAttributes is %v, want %v", event.ObjectAttributes.ID, 1977)
 	}
 
 	if event.User.Name == "" {
 		t.Errorf("Username is %s, want %s", event.User.Name, "Administrator")
 	}
 
-	if event.CommitTitle != "Add new file" {
-		t.Errorf("CommitTitle is %s, want %s", event.CommitTitle, "Add new file")
+	if event.Commit.Timestamp == nil {
+		t.Errorf("Timestamp isn't nil")
+	}
+
+	if name := event.Commit.Author.Name; name != "User" {
+		t.Errorf("Commit Username is %s, want %s", name, "User")
 	}
 }
 
-func TestIssueEventUnmarshal(t *testing.T) {
-	jsonObject := loadFixture("testdata/webhooks/issue.json")
+func TestPushEventUnmarshal(t *testing.T) {
+	jsonObject := loadFixture("testdata/webhooks/push.json")
+	var event *PushEvent
+	err := json.Unmarshal(jsonObject, &event)
 
-	var event *IssueEvent
+	if err != nil {
+		t.Errorf("Push Event can not unmarshaled: %v\n ", err.Error())
+	}
+
+	if event == nil {
+		t.Errorf("Push Event is null")
+	}
+
+	if event.ProjectID != 15 {
+		t.Errorf("ProjectID is %v, want %v", event.ProjectID, 15)
+	}
+
+	if event.UserName != exampleEventUserName {
+		t.Errorf("Username is %s, want %s", event.UserName, exampleEventUserName)
+	}
+
+	if event.Commits[0] == nil || event.Commits[0].Timestamp == nil {
+		t.Errorf("Commit Timestamp isn't nil")
+	}
+
+	if event.Commits[0] == nil || event.Commits[0].Author.Name != "Jordi Mallach" {
+		t.Errorf("Commit Username is %s, want %s", event.UserName, "Jordi Mallach")
+	}
+}
+
+func TestReleaseEventUnmarshal(t *testing.T) {
+	jsonObject := loadFixture("testdata/webhooks/release.json")
+
+	var event *ReleaseEvent
 	err := json.Unmarshal(jsonObject, &event)
 
 	if err != nil {
-		t.Errorf("Deployment Event can not unmarshaled: %v\n ", err.Error())
+		t.Errorf("Release Event can not unmarshaled: %v\n ", err.Error())
 	}
-	if event.Project.ID != 1 {
-		t.Errorf("Project.ID is %v, want %v", event.Project.ID, 1)
+
+	if event == nil {
+		t.Errorf("Release Event is null")
 	}
-	if event.Changes.TotalTimeSpent.Previous != 8100 {
-		t.Errorf("Changes.TotalTimeSpent.Previous is %v , want %v", event.Changes.TotalTimeSpent.Previous, 8100)
+
+	if event.Project.ID != 327622 {
+		t.Errorf("Project.ID is %v, want %v", event.Project.ID, 327622)
 	}
-	if event.Changes.TotalTimeSpent.Current != 9900 {
-		t.Errorf("Changes.TotalTimeSpent.Current is %v , want %v", event.Changes.TotalTimeSpent.Current, 8100)
+
+	if event.Commit.Title != "Merge branch 'example-branch' into 'master'" {
+		t.Errorf("Commit title is %s, want %s", event.Commit.Title, "Merge branch 'example-branch' into 'master'")
+	}
+
+	if len(event.Assets.Sources) != 4 {
+		t.Errorf("Asset sources length is %d, want %d", len(event.Assets.Sources), 4)
+	}
+
+	if event.Assets.Sources[0].Format != "zip" {
+		t.Errorf("First asset source format is %s, want %s", event.Assets.Sources[0].Format, "zip")
+	}
+
+	if len(event.Assets.Links) != 1 {
+		t.Errorf("Asset links length is %d, want %d", len(event.Assets.Links), 1)
+	}
+
+	if event.Assets.Links[0].Name != "Changelog" {
+		t.Errorf("First asset link name is %s, want %s", event.Assets.Links[0].Name, "Changelog")
+	}
+
+	if event.Commit.Author.Name != "User" {
+		t.Errorf("Commit author name is %s, want %s", event.Commit.Author.Name, "User")
 	}
 }
diff --git a/group_hooks_test.go b/group_hooks_test.go
index 0049640..7f0961f 100644
--- a/group_hooks_test.go
+++ b/group_hooks_test.go
@@ -46,6 +46,7 @@ func TestListGroupHooks(t *testing.T) {
 		"pipeline_events": true,
 		"wiki_page_events": true,
 		"deployment_events": true,
+		"releases_events": true,
 		"enable_ssl_verification": true,
 		"created_at": "2012-10-12T17:04:47Z"
 	}
@@ -72,6 +73,7 @@ func TestListGroupHooks(t *testing.T) {
 		PipelineEvents:           true,
 		WikiPageEvents:           true,
 		DeploymentEvents:         true,
+		ReleasesEvents:           true,
 		EnableSSLVerification:    true,
 		CreatedAt:                &datePointer,
 	}}
@@ -102,6 +104,7 @@ func TestGetGroupHook(t *testing.T) {
 	"pipeline_events": true,
 	"wiki_page_events": true,
 	"deployment_events": true,
+	"releases_events": true,
 	"enable_ssl_verification": true,
 	"created_at": "2012-10-12T17:04:47Z"
 }`)
@@ -127,6 +130,7 @@ func TestGetGroupHook(t *testing.T) {
 		PipelineEvents:           true,
 		WikiPageEvents:           true,
 		DeploymentEvents:         true,
+		ReleasesEvents:           true,
 		EnableSSLVerification:    true,
 		CreatedAt:                &datePointer,
 	}
@@ -157,6 +161,7 @@ func TestAddGroupHook(t *testing.T) {
 	"pipeline_events": true,
 	"wiki_page_events": true,
 	"deployment_events": true,
+	"releases_events": true,
 	"enable_ssl_verification": true,
 	"created_at": "2012-10-12T17:04:47Z"
 }`)
@@ -188,6 +193,7 @@ func TestAddGroupHook(t *testing.T) {
 		PipelineEvents:           true,
 		WikiPageEvents:           true,
 		DeploymentEvents:         true,
+		ReleasesEvents:           true,
 		EnableSSLVerification:    true,
 		CreatedAt:                &datePointer,
 	}
@@ -218,6 +224,7 @@ func TestEditGroupHook(t *testing.T) {
 	"pipeline_events": true,
 	"wiki_page_events": true,
 	"deployment_events": true,
+	"releases_events": true,
 	"enable_ssl_verification": true,
 	"created_at": "2012-10-12T17:04:47Z"
 }`)
@@ -249,6 +256,7 @@ func TestEditGroupHook(t *testing.T) {
 		PipelineEvents:           true,
 		WikiPageEvents:           true,
 		DeploymentEvents:         true,
+		ReleasesEvents:           true,
 		EnableSSLVerification:    true,
 		CreatedAt:                &datePointer,
 	}
diff --git a/testdata/webhooks/release.json b/testdata/webhooks/release.json
new file mode 100644
index 0000000..db9af97
--- /dev/null
+++ b/testdata/webhooks/release.json
@@ -0,0 +1,70 @@
+{
+  "id": 8273642,
+  "created_at": "2021-02-25 21:23:34 UTC",
+  "description": "Release!",
+  "name": "1.0.0",
+  "released_at": "2021-02-25 21:23:34 UTC",
+  "tag": "1.0.0",
+  "object_kind": "release",
+  "project": {
+	"id": 327622,
+	"name": "Project Name",
+	"description": "",
+	"web_url": "http://example.com/exm-namespace/example-project",
+	"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+	"git_ssh_url": "git@gitlab.com:exm-namespace/example-project.git",
+	"git_http_url": "http://example.com/exm-namespace/example-project.git",
+	"namespace": "exm-namespace",
+	"visibility_level": 0,
+	"path_with_namespace": "exm-namespace/example-project",
+	"default_branch": "master",
+	"ci_config_path": "",
+	"homepage": "http://example.com/exm-namespace/example-project",
+	"url": "git@gitlab.com:exm-namespace/example-project.git",
+	"ssh_url": "git@gitlab.com:exm-namespace/example-project.git",
+	"http_url": "http://example.com/exm-namespace/example-project.git"
+  },
+  "url": "http://example.com/exm-namespace/example-project/-/releases/1.0.0",
+  "action": "create",
+  "assets": {
+	"count": 4,
+	"links": [
+	  {
+		"id": 1,
+		"external": true,
+		"link_type": "other",
+		"name": "Changelog",
+		"url": "https://example.net/changelog"
+	  }
+	],
+	"sources": [
+	  {
+		"format": "zip",
+		"url": "http://example.com/exm-namespace/example-project/-/archive/1.0.0/example-project-1.0.0.zip"
+	  },
+	  {
+		"format": "tar.gz",
+		"url": "http://example.com/exm-namespace/example-project/-/archive/1.0.0/example-project-1.0.0.tar.gz"
+	  },
+	  {
+		"format": "tar.bz2",
+		"url": "http://example.com/exm-namespace/example-project/-/archive/1.0.0/example-project-1.0.0.tar.bz2"
+	  },
+	  {
+		"format": "tar",
+		"url": "http://example.com/exm-namespace/example-project/-/archive/1.0.0/example-project-1.0.0.tar"
+	  }
+	]
+  },
+  "commit": {
+	"id": "2626dbdb936782b5c54816b1c6d45b1279303c6d",
+	"message": "Merge branch 'example-branch' into 'master'\n\nCheck in this test",
+	"title": "Merge branch 'example-branch' into 'master'",
+	"timestamp": "2021-02-25T21:21:58+00:00",
+	"url": "http://example.com/exm-namespace/example-project/-/commit/2626dbdb936782b5c54816b1c6d45b1279303c6d",
+	"author": {
+	  "name": "User",
+	  "email": "user@gitlab.com"
+	}
+  }
+}