Skip to main content

Custom Hooks

Custom hooks let you inject logic at any point in the event lifecycle without modifying providers or generated wrappers.

Common use cases

  • Consent / privacy — drop events for users who have not given consent
  • PII stripping — remove or hash sensitive property values before dispatch
  • Enrichment — add server-side properties (e.g. experiment assignments, feature flags)
  • Logging / observability — emit structured logs or spans
  • A/B test tagging — annotate events with active experiment variants

Writing a custom hook

import (
"context"
"github.com/dejanradmanovic/event-spec/hooks"
)

type ConsentHook struct {
hooks.UnimplementedHook
consentStore ConsentStore
}

// Before runs once before any provider receives the event.
// Return an error to cancel dispatch (event is Dropped).
func (h *ConsentHook) Before(
ctx context.Context,
hc hooks.HookContext,
hints hooks.HookHints,
) (*hooks.EventEnvelope, error) {
if hc.Context.UserID == "" {
return hc.Envelope, nil // anonymous users always pass
}
if !h.consentStore.HasConsent(hc.Context.UserID, "analytics") {
return nil, hooks.ErrDrop // drop silently
}
return hc.Envelope, nil
}

// Finally runs after every provider result (success or failure).
func (h *ConsentHook) Finally(
ctx context.Context,
hc hooks.HookContext,
result hooks.HookResult,
hints hooks.HookHints,
) {
log.Printf("event=%s provider=%s state=%s", hc.EventName, hc.Provider.Name, result.State)
}

Register it:

client := analytics.NewClient(
analytics.WithProviders(amp),
analytics.WithHooks(&ConsentHook{consentStore: store}),
)

Mutating the envelope

Hooks can add, remove, or modify properties in the EventEnvelope.Properties map. Changes affect what all providers receive:

func (h *EnrichmentHook) Before(ctx context.Context, hc hooks.HookContext, hints hooks.HookHints) (*hooks.EventEnvelope, error) {
hc.Envelope.Properties["server_timestamp"] = time.Now().UTC().Unix()
hc.Envelope.Properties["region"] = h.region
// Remove PII
delete(hc.Envelope.Properties, "email")
return hc.Envelope, nil
}

Hook registration scopes

ScopeHowRuns for
Globalanalytics.AddGlobalHook(h)All clients
Clientanalytics.WithHooks(h)This client only
ProviderProvider.Hooks() return valueThis provider's events only

Execution order within a scope follows registration order. Between scopes: API → Client → Provider in Before; reversed in After/Error/Finally.