Skip to content

Instantly share code, notes, and snippets.

@c-nv-s
Forked from LargatSeif/README.md
Created July 24, 2025 08:21
Show Gist options
  • Save c-nv-s/e68394cd97edec0ef530b68a6fbf6cac to your computer and use it in GitHub Desktop.
Save c-nv-s/e68394cd97edec0ef530b68a6fbf6cac to your computer and use it in GitHub Desktop.
Webhooks for PocketBase

Webhooks for PocketBase (v0.29.0-migrated by me :) )

A simple webhook plugin for PocketBase.

Adds a new collection "webhooks" to the admin interface, to manage webhooks.

Example

The webhook record in the following example send create, update, and delete events in the tickets collection to http://localhost:8080/webhook.

screenshot

Events

Example of a create event, created by an admin user:

{
  "action": "create",
  "collection": "tickets",
  "record": {
    "collectionId": "tickets",
    "collectionName": "tickets",
    "created": "2024-07-07 00:15:57.007Z",
    "description": "",
    "id": "tp0tppxc18slt9a",
    "name": "my ticket",
    "open": true,
    "updated": "2024-07-07 00:15:57.007Z"
  },
  "admin": {
    "id": "k72zfucb9kqmjyx",
    "created": "2024-07-06 23:48:03.137Z",
    "updated": "2024-07-06 23:48:03.137Z",
    "avatar": 0,
    "email": "[email protected]"
  }
}

Usage

The code below shows how to attach the webhook plugin to a PocketBase application. For this example, you need to use PocketBase as a framework, see: Extend with Go - Overview

package main

import (
	"log"

	"github.com/pocketbase/pocketbase"
)

func main() {
	app := pocketbase.New()

	attachWebhooks(app)

	if err := app.Start(); err != nil {
		log.Fatal(err)
	}
}
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/migrations"
)
const webhooksCollection = "webhooks"
type Webhook struct {
ID string `db:"id" json:"id"`
Name string `db:"name" json:"name"`
Collection string `db:"collection" json:"collection"`
Destination string `db:"destination" json:"destination"`
}
func attachWebhooks(app *pocketbase.PocketBase) {
migrations.Register(func(app core.App) error {
collection := core.NewBaseCollection(webhooksCollection)
collection.System = true
collection.Fields.Add(
&core.TextField{
Name: "name",
Required: true,
},
&core.TextField{
Name: "collection",
Required: true,
},
)
collection.Fields.Add(
&core.URLField{
Name: "destination",
Required: true,
},
)
err := app.Save(collection)
if err != nil {
return err
}
return nil
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId(webhooksCollection)
if err != nil {
return err
}
return app.Delete(collection)
}, "1690000000_webhooks.go")
app.OnRecordCreateRequest().BindFunc(func(e *core.RecordRequestEvent) error {
event(app, "create", e.Record.Collection().Name, e.Record, e.Auth, e.Request.Context())
return e.Next()
})
app.OnRecordUpdateRequest().BindFunc(func(e *core.RecordRequestEvent) error {
event(app, "update", e.Record.Collection().Name, e.Record, e.Auth, e.Request.Context())
return e.Next()
})
app.OnRecordDeleteRequest().BindFunc(func(e *core.RecordRequestEvent) error {
event(app, "delete", e.Collection.Name, e.Record, e.Auth, e.Request.Context())
return e.Next()
})
}
type Payload struct {
Action string `json:"action"`
Collection string `json:"collection"`
Record *core.Record `json:"record"`
Auth *core.Record `json:"omitempty"`
}
func event(app *pocketbase.PocketBase, action, collection string, record *core.Record, authRecord *core.Record, requestCtx context.Context) error {
var webhooks []Webhook
if err := app.DB().
Select().
From(webhooksCollection).
Where(dbx.HashExp{"collection": collection}).
All(&webhooks); err != nil {
return err
}
if len(webhooks) == 0 {
return nil
}
payload, err := json.Marshal(&Payload{
Action: action,
Collection: collection,
Record: record,
Auth: authRecord,
})
if err != nil {
return err
}
for _, webhook := range webhooks {
if err := sendWebhook(requestCtx, webhook, payload); err != nil {
app.Logger().Error("failed to send webhook", "action", action, "name", webhook.Name, "collection", webhook.Collection, "destination", webhook.Destination, "error", err.Error())
} else {
app.Logger().Info("webhook sent", "action", action, "name", webhook.Name, "collection", webhook.Collection, "destination", webhook.Destination)
}
}
return nil
}
func sendWebhook(ctx context.Context, webhook Webhook, payload []byte) error {
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, webhook.Destination, bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
b, _ := io.ReadAll(resp.Body)
return fmt.Errorf("failed to send webhook: %s", string(b))
}
return nil
}
{
"action": "create",
"collection": "tickets",
"record": {
"collectionId": "tickets",
"collectionName": "tickets",
"created": "2024-07-07 01:31:02.110Z",
"description": "",
"id": "a0152zdnfzgow4z",
"name": "test",
"open": true,
"updated": "2024-07-07 01:31:02.110Z"
},
"auth": {
"avatar": "",
"collectionId": "_pb_users_auth_",
"collectionName": "users",
"created": "2024-07-07 01:29:57.912Z",
"emailVisibility": false,
"id": "u_test",
"name": "Alivia Cartwright",
"updated": "2024-07-07 01:29:57.912Z",
"username": "u_test",
"verified": true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment