Docs/Controlplane/Events & Webhooks
Open

Reading3 min
Updated31 Jul 2026
Sourcev1/subsystems/events.mdx

Every state change in Ctrl Plane publishes an event. You can subscribe to events within your application for custom logic, or configure webhooks to notify external systems.

Event structure01

type Event struct {
    ID         id.ID          `json:"id,omitzero"`
    Type       Type           `json:"type"        db:"type"`
    TenantID   string         `json:"tenant_id"   db:"tenant_id"`
    InstanceID id.ID          `json:"instance_id,omitzero" db:"instance_id"`
    ActorID    string         `json:"actor_id"    db:"actor_id"`
    Payload    map[string]any `json:"payload"     db:"payload"`
    Timestamp  time.Time      `json:"timestamp"   db:"timestamp"`
}

Events are created with a builder pattern:

evt := event.NewEvent(event.InstanceCreated, tenantID).
    WithInstance(instanceID).
    WithActor(userID).
    WithPayload(map[string]any{
        "name":  "api-server",
        "image": "myapp:v1.0",
    })

Event types02

Events are organized by subsystem:

Instance events: InstanceCreated, InstanceStarted, InstanceStopped, InstanceFailed, InstanceDeleted, InstanceScaled, InstanceSuspended, InstanceUnsuspended

Deploy events: DeployStarted, DeploySucceeded, DeployFailed, DeployRolledBack

Health events: HealthCheckPassed, HealthCheckFailed, HealthDegraded, HealthRecovered

Network events: DomainAdded, DomainVerified, DomainRemoved, CertProvisioned, CertExpiring

Admin events: TenantCreated, TenantSuspended, TenantDeleted, QuotaExceeded

Bus interface03

The event bus handles publishing and subscription:

type Bus interface {
    Publish(ctx context.Context, event *Event) error
    Subscribe(handler Handler, types ...Type) Subscription
    Close() error
}

type Handler func(ctx context.Context, event *Event) error

Subscribe to events

sub := cp.Events.Subscribe(func(ctx context.Context, evt *event.Event) error {
    log.Printf("instance %s: %s", evt.InstanceID, evt.Type)
    return nil
}, event.InstanceCreated, event.InstanceFailed)

// Later, when you're done:
sub.Unsubscribe()

Subscribe to all events by passing no types:

sub := cp.Events.Subscribe(handler) // receives everything

Implementations04

Ctrl Plane ships with an in-memory event bus. Replace it with a durable bus for production:

cp, err := app.New(
    app.WithEventBus(natsEventBus), // or redis, kafka, etc.
)

The Bus interface is small enough that adapting any message broker is straightforward.

Webhooks05

Webhooks deliver events to external HTTP endpoints:

type Webhook struct {
    ctrlplane.Entity
    TenantID string   `json:"tenant_id" db:"tenant_id"`
    URL      string   `json:"url"       db:"url"`
    Secret   string   `json:"-"         db:"secret"` // HMAC signing key
    Events   []Type   `json:"events"    db:"events"`
    Active   bool     `json:"active"    db:"active"`
}

When a matching event occurs, the webhook system sends an HTTP POST to the configured URL with the event payload. Deliveries are tracked with retry logic:

type WebhookDelivery struct {
    ctrlplane.Entity
    WebhookID  id.ID      `json:"webhook_id"  db:"webhook_id"`
    EventID    id.ID      `json:"event_id"    db:"event_id"`
    StatusCode int        `json:"status_code" db:"status_code"`
    Attempts   int        `json:"attempts"    db:"attempts"`
    NextRetry  *time.Time `json:"next_retry"  db:"next_retry"`
    Error      string     `json:"error"       db:"error"`
}