diff --git a/pkg/migration/20230913202615.go b/pkg/migration/20230913202615.go
new file mode 100644
index 000000000..1df44f1e9
--- /dev/null
+++ b/pkg/migration/20230913202615.go
@@ -0,0 +1,50 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public Licensee as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public Licensee for more details.
+//
+// You should have received a copy of the GNU Affero General Public Licensee
+// along with this program. If not, see .
+
+package migration
+
+import (
+ "src.techknowlogick.com/xormigrate"
+ "time"
+ "xorm.io/xorm"
+)
+
+type webhooks20230913202615 struct {
+ ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"webhook"`
+ TargetURL string `xorm:"not null" valid:"minstringlength(1)" minLength:"1" json:"target_url"`
+ Events []string `xorm:"JSON not null" valid:"minstringlength(1)" minLength:"1" json:"event"`
+ ProjectID int64 `xorm:"not null" json:"project_id" param:"project"`
+ CreatedByID int64 `xorm:"bigint not null" json:"-"`
+ Created time.Time `xorm:"created not null" json:"created"`
+ Updated time.Time `xorm:"updated not null" json:"updated"`
+}
+
+func (webhooks20230913202615) TableName() string {
+ return "webhooks"
+}
+
+func init() {
+ migrations = append(migrations, &xormigrate.Migration{
+ ID: "20230913202615",
+ Description: "",
+ Migrate: func(tx *xorm.Engine) error {
+ return tx.Sync2(webhooks20230913202615{})
+ },
+ Rollback: func(tx *xorm.Engine) error {
+ return nil
+ },
+ })
+}
diff --git a/pkg/models/webhooks.go b/pkg/models/webhooks.go
new file mode 100644
index 000000000..561b7a361
--- /dev/null
+++ b/pkg/models/webhooks.go
@@ -0,0 +1,94 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public Licensee as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public Licensee for more details.
+//
+// You should have received a copy of the GNU Affero General Public Licensee
+// along with this program. If not, see .
+
+package models
+
+import (
+ "code.vikunja.io/api/pkg/user"
+ "code.vikunja.io/web"
+ "time"
+ "xorm.io/xorm"
+)
+
+type Webhook struct {
+ ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"webhook"`
+ TargetURL string `xorm:"not null" valid:"minstringlength(1)" minLength:"1" json:"target_url"`
+ Events []string `xorm:"JSON not null" valid:"minstringlength(1)" minLength:"1" json:"event"`
+ ProjectID int64 `xorm:"not null" json:"project_id" param:"project"`
+
+ // The user who initially created the webhook target.
+ CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-"`
+ CreatedByID int64 `xorm:"bigint not null" json:"-"`
+
+ // A timestamp when this webhook target was created. You cannot change this value.
+ Created time.Time `xorm:"created not null" json:"created"`
+ // A timestamp when this webhook target was last updated. You cannot change this value.
+ Updated time.Time `xorm:"updated not null" json:"updated"`
+
+ web.CRUDable `xorm:"-" json:"-"`
+ web.Rights `xorm:"-" json:"-"`
+}
+
+func (w *Webhook) TableName() string {
+ return "webhooks"
+}
+
+func (w *Webhook) Create(s *xorm.Session, a web.Auth) (err error) {
+ // TODO: check valid webhook events
+ w.CreatedByID = a.GetID()
+ _, err = s.Insert(w)
+ return
+}
+
+func (w *Webhook) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
+ p := &Project{ID: w.ProjectID}
+ can, _, err := p.CanRead(s, a)
+ if err != nil {
+ return nil, 0, 0, err
+ }
+ if !can {
+ return nil, 0, 0, ErrGenericForbidden{}
+ }
+
+ ws := []*Webhook{}
+ err = s.Where("project_id = ?", w.ProjectID).
+ Limit(getLimitFromPageIndex(page, perPage)).
+ Find(&ws)
+ if err != nil {
+ return
+ }
+
+ total, err := s.Where("project_id = ?", w.ProjectID).
+ Count(&Webhook{})
+ if err != nil {
+ return
+ }
+
+ return ws, len(ws), total, err
+}
+
+func (w *Webhook) Update(s *xorm.Session, a web.Auth) (err error) {
+ // TODO validate webhook events
+ _, err = s.Where("id = ?", w.ID).
+ Cols("events").
+ Update(w)
+ return
+}
+
+func (w *Webhook) Delete(s *xorm.Session, a web.Auth) (err error) {
+ _, err = s.Where("id = ?", w.ID).Delete(&Webhook{})
+ return
+}
diff --git a/pkg/models/webhooks_rights.go b/pkg/models/webhooks_rights.go
new file mode 100644
index 000000000..fed8483be
--- /dev/null
+++ b/pkg/models/webhooks_rights.go
@@ -0,0 +1,42 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-present Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public Licensee as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public Licensee for more details.
+//
+// You should have received a copy of the GNU Affero General Public Licensee
+// along with this program. If not, see .
+
+package models
+
+import (
+ "code.vikunja.io/web"
+ "xorm.io/xorm"
+)
+
+func (w *Webhook) CanRead(s *xorm.Session, a web.Auth) (bool, int, error) {
+ p := &Project{ID: w.ProjectID}
+ return p.CanRead(s, a)
+}
+
+func (w *Webhook) CanDelete(s *xorm.Session, a web.Auth) (bool, error) {
+ p := &Project{ID: w.ProjectID}
+ return p.CanUpdate(s, a)
+}
+
+func (w *Webhook) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
+ p := &Project{ID: w.ProjectID}
+ return p.CanUpdate(s, a)
+}
+
+func (w *Webhook) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
+ p := &Project{ID: w.ProjectID}
+ return p.CanUpdate(s, a)
+}