Webhook subscriptions

A webhook subscription is your endpoint registration with Huudis: a URL Huudis POSTs to whenever an event you've selected fires. The events catalogue ships in code (huudis.*.v1 slugs); the subscription wires a URL to a subset of them and gives you a signing secret. The Go SDK exposes them on client.WebhookSubscriptions. For verifying signatures see SDK overview → Verify webhook signatures; for the HTTP surface see API → Webhook subscriptions.

Field on the Client

client.WebhookSubscriptions — type *huudis.WebhookSubscriptionsResource.

Methods

List / Get

func (r *WebhookSubscriptionsResource) List(ctx context.Context, auth RequestAuth) ([]WebhookSubscription, error)
func (r *WebhookSubscriptionsResource) Get(ctx context.Context, id string, auth RequestAuth) (*WebhookSubscription, error)

List returns every subscription in the workspace. Get fetches one by ID. Neither returns Secret — only Create and RotateSecret do.

subs, _ := client.WebhookSubscriptions.List(ctx, huudis.RequestAuth{})
for _, s := range subs {
    fmt.Println(s.ID, s.URL, len(s.Events), "events")
}

one, _ := client.WebhookSubscriptions.Get(ctx, "whsub_01HX...", huudis.RequestAuth{})
_ = one

Create

Signature. func (r *WebhookSubscriptionsResource) Create(ctx context.Context, in CreateWebhookSubscriptionInput, auth RequestAuth) (*WebhookSubscription, error)

Registers a new subscription. URL must be an https:// URL on a public address: a private, loopback or link-local one (or a name that resolves to one) is refused with BLOCKED_TARGET, and redirects aren't followed. Events is a slice of slugs from EventsCatalog(); passing ["*"] subscribes to everything.

sub, err := client.WebhookSubscriptions.Create(ctx, huudis.CreateWebhookSubscriptionInput{
    URL:         "https://acme.example.com/webhooks/huudis",
    Events:      []string{"huudis.user.created.v1", "huudis.user.disabled.v1"},
    Description: "Sync to internal user table",
}, huudis.RequestAuth{})
if err != nil {
    return err
}
log.Println("webhook secret:", sub.Secret) // SAVE NOW — never shown again

Secret is shown exactly once. It's present on the create response and on RotateSecret. Store it in your secret manager immediately. Without it, you can't verify incoming requests.

Update

Signature. func (r *WebhookSubscriptionsResource) Update(ctx context.Context, id string, patch map[string]any, auth RequestAuth) (*WebhookSubscription, error)

PATCH — only keys you pass change. Toggle "active": false to pause deliveries without losing config.

_, _ = client.WebhookSubscriptions.Update(ctx, "whsub_01HX...", map[string]any{
    "events": append(existing.Events, "huudis.session.revoked.v1"),
}, huudis.RequestAuth{})

// Pause for maintenance.
_, _ = client.WebhookSubscriptions.Update(ctx, "whsub_01HX...", map[string]any{
    "active": false,
}, huudis.RequestAuth{})

RotateSecret

Signature. func (r *WebhookSubscriptionsResource) RotateSecret(ctx context.Context, id string, auth RequestAuth) (*RotateSecretResult, error)

Mints a fresh secret. The old one is invalidated immediately — there's no overlap window. Coordinate with deploys: write the new secret into your consumer first, then issue the rotate.

res, err := client.WebhookSubscriptions.RotateSecret(ctx, "whsub_01HX...", huudis.RequestAuth{})
if err != nil {
    return err
}
pushToSecretStore(res.Secret)

Delete

Signature. func (r *WebhookSubscriptionsResource) Delete(ctx context.Context, id string, auth RequestAuth) error

Removes the subscription. In-flight deliveries already in the retry queue may still fire once (within ~5 minutes); after that, silence.

_ = client.WebhookSubscriptions.Delete(ctx, "whsub_01HX...", huudis.RequestAuth{})

ListDeliveries

Signature. func (r *WebhookSubscriptionsResource) ListDeliveries(ctx context.Context, id string, p ListWebhookDeliveriesParams, auth RequestAuth) ([]WebhookDelivery, error)

Recent deliveries. Status: "pending", "success", "failed". Limit caps at 100; deliveries beyond 30 days are pruned.

fails, _ := client.WebhookSubscriptions.ListDeliveries(ctx, "whsub_01HX...",
    huudis.ListWebhookDeliveriesParams{Status: "failed", Limit: 50},
    huudis.RequestAuth{})
for _, d := range fails {
    fmt.Println(d.EventType, d.HttpStatus, d.LastAttemptAt)
}

ReplayDelivery

Signature. func (r *WebhookSubscriptionsResource) ReplayDelivery(ctx context.Context, deliveryID string, auth RequestAuth) error

Re-queues one delivery for another shot. Use when your endpoint had a transient outage.

_ = client.WebhookSubscriptions.ReplayDelivery(ctx, "whd_01HX...", huudis.RequestAuth{})

EventsCatalog

Signature. func (r *WebhookSubscriptionsResource) EventsCatalog(ctx context.Context, auth RequestAuth) ([]WebhookEventCatalogEntry, error)

Returns every event slug Huudis can emit, with description + payload schema. Use to build a "pick events" UI.

catalog, _ := client.WebhookSubscriptions.EventsCatalog(ctx, huudis.RequestAuth{})
for _, e := range catalog {
    fmt.Println(e.Type, "—", e.Description)
}

Types

type WebhookSubscription struct {
    ID          string   `json:"id"`
    AccountID   string   `json:"accountId"`
    URL         string   `json:"url"`
    Events      []string `json:"events"`
    Description string   `json:"description,omitempty"`
    Active      bool     `json:"active"`
    Secret      string   `json:"secret,omitempty"` // only on create + RotateSecret
    CreatedAt   string   `json:"createdAt"`
    UpdatedAt   string   `json:"updatedAt"`
}

type WebhookDelivery struct {
    ID             string `json:"id"`
    SubscriptionID string `json:"subscriptionId"`
    EventID        string `json:"eventId"`
    EventType      string `json:"eventType"`
    Status         string `json:"status"`         // "pending" | "success" | "failed"
    HttpStatus     int    `json:"httpStatus,omitempty"`
    ResponseBody   string `json:"responseBody,omitempty"`
    AttemptCount   int    `json:"attemptCount"`
    LastAttemptAt  string `json:"lastAttemptAt,omitempty"`
    CreatedAt      string `json:"createdAt"`
}

See API → Webhook subscriptions for delivery semantics (retry schedule, timeout, max attempts).

Common patterns

Verify on receive (net/http)

import (
    huudis "github.com/hachimi-cat/huudis-go"
)

http.HandleFunc("/webhooks/huudis", func(w http.ResponseWriter, r *http.Request) {
    raw, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, err.Error(), 400)
        return
    }
    if !huudis.VerifyWebhookSignature(raw, r.Header.Get("X-Huudis-Signature"), os.Getenv("HUUDIS_WEBHOOK_SECRET")) {
        http.Error(w, "bad signature", 400)
        return
    }
    var event map[string]any
    _ = json.Unmarshal(raw, &event)
    // handle event...
    w.WriteHeader(204)
})

The signature must be verified against the raw body bytes — never the JSON-parsed object (re-serializing changes whitespace and the HMAC won't match).

Audit failed deliveries

A daily cron that surfaces failing endpoints:

type FailingSub struct {
    URL      string
    Failures int
}

func failingSubs(ctx context.Context, c *huudis.Client) ([]FailingSub, error) {
    subs, err := c.WebhookSubscriptions.List(ctx, huudis.RequestAuth{})
    if err != nil {
        return nil, err
    }
    var out []FailingSub
    for _, s := range subs {
        fails, _ := c.WebhookSubscriptions.ListDeliveries(ctx, s.ID,
            huudis.ListWebhookDeliveriesParams{Status: "failed", Limit: 100},
            huudis.RequestAuth{})
        if len(fails) > 0 {
            out = append(out, FailingSub{URL: s.URL, Failures: len(fails)})
        }
    }
    return out, nil
}

Re-drive after a multi-hour outage

func redrive(ctx context.Context, c *huudis.Client, subID string) error {
    fails, err := c.WebhookSubscriptions.ListDeliveries(ctx, subID,
        huudis.ListWebhookDeliveriesParams{Status: "failed", Limit: 100},
        huudis.RequestAuth{})
    if err != nil {
        return err
    }
    for _, d := range fails {
        if err := c.WebhookSubscriptions.ReplayDelivery(ctx, d.ID, huudis.RequestAuth{}); err != nil {
            return err
        }
    }
    return nil
}

Per-environment subscriptions

Subscribe production webhooks to your production endpoint and staging webhooks to a separate endpoint — never share a subscription across envs:

env := os.Getenv("APP_ENV")
_, _ = client.WebhookSubscriptions.Create(ctx, huudis.CreateWebhookSubscriptionInput{
    URL:         fmt.Sprintf("https://%s.acme.example.com/webhooks/huudis", env),
    Events:      []string{"*"},
    Description: "Acme (" + env + ")",
}, huudis.RequestAuth{})

Errors

Code Status Cause
validation_error 400 Bad URL (non-HTTPS), unknown event slug, description too long.
not_found 404 Subscription or delivery ID doesn't exist or lives in another workspace.
state_conflict 409 Replay on a non-failed delivery; rotate on a deleted sub.
forbidden 403 Caller lacks huudis:webhook:write.

Next

Huudis Docs · Webhook subscriptions | Huudis