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
Secretis shown exactly once. It's present on the create response and onRotateSecret. 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
- SDK overview → Verify webhook signatures — the cryptographic verify step.
- API → Webhook subscriptions — HTTP-level reference + retry schedule.
- Webhook events catalogue — every slug and payload schema.