webhook

package
v0.0.0-...-c133f9a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package webhook persists incoming GitHub webhook deliveries and reconciles them asynchronously.

EventProcessor stores each delivery as a PENDING Event and returns 200 immediately, then dispatches each Event to the Reconciler registered for its event type. The storage is abstracted by Store so that each application can keep the events in its own table.

Index

Constants

This section is empty.

Variables

View Source
var ErrDuplicateDelivery = xerrors.New("duplicate delivery")

ErrDuplicateDelivery is returned by Store.Create when the delivery ID is already stored.

Functions

This section is empty.

Types

type Event

type Event struct {
	Id         int32
	DeliveryId string
	EventType  string
	Action     string
	Payload    []byte
	State      State
	Status     []byte
	LastError  string
	CreatedAt  time.Time
	UpdatedAt  time.Time
}

Event is a single webhook delivery.

func (*Event) Finalize

func (e *Event) Finalize(recErr error)

Finalize decides the state of e after Reconcile returned recErr.

  • recErr != nil: FAILED
  • e.State is PROCESSING: SUCCEEDED
  • otherwise: the state set by the reconciler is preserved

func (*Event) ReadStatus

func (e *Event) ReadStatus(out any) error

ReadStatus unmarshals the status of e into out. An empty status leaves out at its zero value.

func (*Event) WriteStatus

func (e *Event) WriteStatus(in any) error

WriteStatus marshals in into the status of e.

type EventProcessor

type EventProcessor struct {
	// contains filtered or unexported fields
}

EventProcessor receives GitHub webhook deliveries with ServeHTTP and reconciles them with Run.

ServeHTTP stores each delivery as a PENDING event and hands it to Run in the same process. Run also polls the Store for PENDING and FAILED events to retry them and to process the events received by other processes. Each event is dispatched to the Reconciler registered for the event type.

Run can be called concurrently in multiple processes against the same Store. An event is processed only by the process that claimed it. An event waiting longer than maxProcessingDuration since it was created is moved to EXPIRED. An event left in PROCESSING longer than stuckProcessingTimeout is moved back to PENDING.

func NewEventProcessor

func NewEventProcessor(store Store, interval, maxProcessingDuration time.Duration) *EventProcessor

func (*EventProcessor) Register

func (p *EventProcessor) Register[T any](rec Reconciler[T])

Register adds rec. An event whose payload can not be decoded into T is marked as FAILED.

func (*EventProcessor) Run

func (p *EventProcessor) Run(ctx context.Context)

Run reconciles the stored events until ctx is cancelled.

func (*EventProcessor) ServeHTTP

func (p *EventProcessor) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP stores a GitHub webhook delivery as a PENDING event.

type Reconciler

type Reconciler[T any] interface {
	EventType() string
	Reconcile(ctx context.Context, ev *Event, payload *T) error
}

Reconciler processes an Event of a single event type. payload is the payload of ev decoded into T.

Implementations must be idempotent. Progress should be checkpointed into ev.Status with Event.WriteStatus so that re-running resumes the work instead of duplicating side effects. A reconciler may set ev.State to a terminal state (e.g. StateSkipped) or back to StatePending to be retried. Otherwise the state is decided by Event.Finalize.

type State

type State int32

State is the lifecycle state of an Event. The values are persisted as is.

const (
	StatePending    State = 0
	StateProcessing State = 1
	StateSucceeded  State = 2
	StateFailed     State = 3
	StateExpired    State = 4
	StateSkipped    State = 5
)

type Store

type Store interface {
	// Create inserts ev and sets ev.Id. It returns ErrDuplicateDelivery if the delivery ID is already stored.
	Create(ctx context.Context, ev *Event) error
	// ListByState returns the events in any of the given states.
	ListByState(ctx context.Context, states ...State) ([]*Event, error)
	// Claim updates the state of ev to PROCESSING only if it is PENDING or FAILED, and reports whether it was updated.
	Claim(ctx context.Context, ev *Event) (bool, error)
	// Update persists State, Status and LastError of ev.
	Update(ctx context.Context, ev *Event) error
}

Store persists Events.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL