Documentation
¶
Overview ¶
Package webhookd is the official Go SDK for NimbusNexus Webhooks (webhookd).
- Verify — verify an incoming webhook's HMAC signature (for subscribers).
- Client — publish events to webhookd and manage endpoints/keys/deliveries (for producers).
webhookd signs every delivery as HMAC_SHA256(secret, "<timestamp>." + rawBody) (its default timestamped mode) and sends X-Webhook-Signature: sha256=<hex> plus X-Webhook-Timestamp (unix seconds). A subscriber MUST verify the signature to prove the request genuinely came from webhookd and was not tampered with. This mirrors delivery_core.webhook_outbox.{sign,verify} byte-for-byte.
Index ¶
- Constants
- func Sign(secret string, body []byte, timestamp int64) string
- func Verify(secret string, body []byte, signature string, opts *VerifyOptions) bool
- type APIError
- type ApiKey
- type Client
- func (c *Client) CreateAPIKey(ctx context.Context, opts *CreateAPIKeyOptions) (*ApiKey, error)
- func (c *Client) CreateEndpoint(ctx context.Context, endpointURL string, opts *CreateEndpointOptions) (*Endpoint, error)
- func (c *Client) DeleteEndpoint(ctx context.Context, id string) error
- func (c *Client) Drain(ctx context.Context, opts *DrainOptions) (DrainResult, error)
- func (c *Client) EnableEndpoint(ctx context.Context, id string) (*Endpoint, error)
- func (c *Client) Enqueue(ctx context.Context, eventType string, payload map[string]any, ...) (string, error)
- func (c *Client) GetEndpoint(ctx context.Context, id string) (*Endpoint, error)
- func (c *Client) ListDeliveries(ctx context.Context, opts *ListDeliveriesOptions) (*Page[Delivery], error)
- func (c *Client) ListEndpoints(ctx context.Context, opts *ListEndpointsOptions) (*Page[Endpoint], error)
- func (c *Client) Publish(ctx context.Context, eventType string, payload map[string]any, ...) (*Event, error)
- func (c *Client) Redeliver(ctx context.Context, id string) (*Delivery, error)
- func (c *Client) RevokeAPIKey(ctx context.Context, id string) error
- func (c *Client) RotateEndpointSecret(ctx context.Context, id string) (*Endpoint, error)
- func (c *Client) StartDrainer(ctx context.Context, interval time.Duration, opts *DrainOptions)
- func (c *Client) StopDrainer()
- func (c *Client) UpdateEndpoint(ctx context.Context, id string, patch Patch) (*Endpoint, error)
- type CreateAPIKeyOptions
- type CreateEndpointOptions
- type Delivery
- type DrainOptions
- type DrainResult
- type Endpoint
- type EnqueueOptions
- type Error
- type Event
- type FileStore
- func (s *FileStore) Close() error
- func (s *FileStore) ListPending(_ context.Context, limit int) ([]Record, error)
- func (s *FileStore) MarkFailed(_ context.Context, id string, lastError *string, attempts int, ...) error
- func (s *FileStore) MarkSent(_ context.Context, id string) error
- func (s *FileStore) Save(_ context.Context, record Record) error
- func (s *FileStore) Size(_ context.Context) (int, error)
- type ListDeliveriesOptions
- type ListEndpointsOptions
- type MemoryStore
- func (s *MemoryStore) Close() error
- func (s *MemoryStore) ListPending(_ context.Context, limit int) ([]Record, error)
- func (s *MemoryStore) MarkFailed(_ context.Context, id string, lastError *string, attempts int, ...) error
- func (s *MemoryStore) MarkSent(_ context.Context, id string) error
- func (s *MemoryStore) Save(_ context.Context, record Record) error
- func (s *MemoryStore) Size(_ context.Context) (int, error)
- type Option
- type Page
- type Patch
- type PublishOptions
- type Record
- type Store
- type Subscription
- type VerifyOptions
Constants ¶
const (
// DefaultToleranceSeconds is webhookd's default replay window for signature verification.
DefaultToleranceSeconds = 300
)
const Version = "0.3.0"
Version tracks the release tag; keep in lockstep with the Python/TypeScript SDKs.
Variables ¶
This section is empty.
Functions ¶
func Sign ¶
Sign returns the "sha256=<hex>" signature webhookd would send for body, signing over "<timestamp>." + body. Mainly useful for tests; subscribers call Verify.
func Verify ¶
func Verify(secret string, body []byte, signature string, opts *VerifyOptions) bool
Verify reports whether signature is a valid webhookd signature for body.
Pass the EXACT bytes you received as body — do not re-serialize the JSON, or the signature won't match. When opts.Timestamp is set, the signature is rejected if it is older/newer than the tolerance window (replay protection). X-Webhook-Signature carries one token normally, or several comma-separated tokens during a signing-secret rotation (dual-sign overlap); Verify accepts the request if ANY token matches, using a constant-time comparison. A malformed X-Webhook-Timestamp returns false rather than panicking.
Types ¶
type APIError ¶
APIError is a non-2xx response from the webhookd API.
It carries the M5a error envelope: a stable machine Code (e.g. "rate_limited", "not_found", "validation_error") and a human Message, plus the HTTP StatusCode.
type ApiKey ¶
type ApiKey struct {
Id string `json:"id"`
Name string `json:"name"`
Scope string `json:"scope"`
Key *string `json:"key,omitempty"`
CreatedBy *string `json:"created_by,omitempty"`
CreatedAt *string `json:"created_at,omitempty"`
ExpiresAt *string `json:"expires_at,omitempty"`
}
ApiKey is an API key, as returned by POST /v1/api-keys (webhookd's ApiKeyOut). Key is present ONLY on the create response (returned exactly once).
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client publishes events to webhookd and manages endpoints, API keys and deliveries.
Authenticate with a per-tenant API key (whsk_…) or a service token. Transient failures (connection errors, 429, 5xx) are retried with capped exponential backoff; a 429 honours its Retry-After header. Other non-2xx responses return an *APIError carrying the server's {error: {code, message}} envelope.
func New ¶
New creates a Client for the webhookd instance at baseURL authenticated with apiKey. Trailing slashes are trimmed from baseURL.
func (*Client) CreateAPIKey ¶
CreateAPIKey creates an API key (POST /v1/api-keys). The response includes the raw key exactly once — persist it.
func (*Client) CreateEndpoint ¶
func (c *Client) CreateEndpoint(ctx context.Context, endpointURL string, opts *CreateEndpointOptions) (*Endpoint, error)
CreateEndpoint creates an endpoint (POST /v1/endpoints). The response includes the signing secret exactly once — persist it.
func (*Client) DeleteEndpoint ¶
DeleteEndpoint deletes an endpoint (DELETE /v1/endpoints/{id}, 204).
func (*Client) Drain ¶
func (c *Client) Drain(ctx context.Context, opts *DrainOptions) (DrainResult, error)
Drain sends buffered records to webhookd. It pulls a due batch (oldest first) and POSTs each to /v1/events with header Idempotency-Key = record.ID (so a re-drain after a crash never double-publishes — webhookd dedupes). On 2xx the record is marked sent; on error attempts is bumped and the record is rescheduled with capped exponential backoff, or parked dead once attempts reaches maxAttempts (the WithOnDead hook fires). Requires a store (WithStore).
func (*Client) EnableEndpoint ¶
EnableEndpoint re-enables an endpoint webhookd auto-disabled after repeated failures (POST /v1/endpoints/{id}/enable).
func (*Client) Enqueue ¶
func (c *Client) Enqueue(ctx context.Context, eventType string, payload map[string]any, opts *EnqueueOptions) (string, error)
Enqueue durably buffers one event and returns its id WITHOUT any network call.
The id is opts.IdempotencyKey (if non-empty) or a fresh UUID v4; it becomes the Idempotency-Key on every later Drain send, so re-draining after a crash never double-publishes. Requires a store (WithStore).
func (*Client) GetEndpoint ¶
GetEndpoint fetches a single endpoint by id (GET /v1/endpoints/{id}).
func (*Client) ListDeliveries ¶
func (c *Client) ListDeliveries(ctx context.Context, opts *ListDeliveriesOptions) (*Page[Delivery], error)
ListDeliveries lists deliveries (GET /v1/deliveries). Only the filters you provide are forwarded.
func (*Client) ListEndpoints ¶
func (c *Client) ListEndpoints(ctx context.Context, opts *ListEndpointsOptions) (*Page[Endpoint], error)
ListEndpoints lists endpoints for an environment (default "prod") (GET /v1/endpoints).
func (*Client) Publish ¶
func (c *Client) Publish(ctx context.Context, eventType string, payload map[string]any, opts *PublishOptions) (*Event, error)
Publish publishes one event (POST /v1/events). idempotency makes the publish safe to retry — a replay returns the original event without re-fanning-out.
func (*Client) Redeliver ¶
Redeliver re-enqueues a delivery for another attempt (POST /v1/deliveries/{id}/redeliver).
func (*Client) RevokeAPIKey ¶
RevokeAPIKey revokes an API key (DELETE /v1/api-keys/{id}, 204).
func (*Client) RotateEndpointSecret ¶
RotateEndpointSecret rotates an endpoint's signing secret (POST /v1/endpoints/{id}/rotate-secret). The response includes the new secret exactly once.
func (*Client) StartDrainer ¶
StartDrainer launches a background goroutine that calls Drain every interval until StopDrainer (or ctx cancellation). A per-drain error is swallowed so a transient outage never kills the loop. Idempotent — a second call while one is running is a no-op. Requires a store (WithStore).
func (*Client) StopDrainer ¶
func (c *Client) StopDrainer()
StopDrainer signals the background drainer to stop and waits for it to exit. Safe to call when none is running.
type CreateAPIKeyOptions ¶
CreateAPIKeyOptions carries the optional arguments to Client.CreateAPIKey. Scope defaults to "admin" when empty; ExpiresInDays is sent only when non-nil.
type CreateEndpointOptions ¶
type CreateEndpointOptions struct {
Environment string
Application string
Subscriptions []Subscription
Secret *string
MaxAttempts *int
RetrySchedule []int
Description *string
CustomHeaders map[string]string
DeliveryTimeoutMs *int
}
CreateEndpointOptions carries the optional arguments to Client.CreateEndpoint. Environment defaults to "prod" and Application to "default" when empty. Every other field is sent only when non-nil so the server applies its own default.
type Delivery ¶
type Delivery struct {
Id string `json:"id"`
EndpointID string `json:"endpoint_id"`
EventID *string `json:"event_id,omitempty"`
EventType *string `json:"event_type,omitempty"`
Status string `json:"status"`
Attempts *int `json:"attempts,omitempty"`
CreatedAt *string `json:"created_at,omitempty"`
UpdatedAt *string `json:"updated_at,omitempty"`
}
Delivery is a delivery attempt record, as returned by the deliveries endpoints (webhookd's DeliveryOut).
type DrainOptions ¶
DrainOptions overrides the client defaults for a single Drain. A zero field falls back to the client's configured value.
type DrainResult ¶
type DrainResult struct {
// Sent is the number of records delivered (2xx) and marked sent this drain.
Sent int
// Failed is the number of records that failed this drain (rescheduled or newly parked dead).
Failed int
// Remaining is the number of records still buffered afterwards (Store.Size).
Remaining int
}
DrainResult summarises one Drain call.
type Endpoint ¶
type Endpoint struct {
Id string `json:"id"`
URL string `json:"url"`
Environment string `json:"environment"`
Application string `json:"application"`
Status string `json:"status"`
Subscriptions []Subscription `json:"subscriptions"`
Secret *string `json:"secret,omitempty"`
MaxAttempts *int `json:"max_attempts,omitempty"`
RetrySchedule []int `json:"retry_schedule,omitempty"`
Description *string `json:"description,omitempty"`
CustomHeaders map[string]string `json:"custom_headers,omitempty"`
DeliveryTimeoutMs *int `json:"delivery_timeout_ms,omitempty"`
CreatedAt *string `json:"created_at,omitempty"`
UpdatedAt *string `json:"updated_at,omitempty"`
}
Endpoint is an endpoint, as returned by the management endpoints (webhookd's EndpointOut). Fields mirror the server's snake_case wire shape verbatim. Secret is present ONLY on the create + rotate-secret responses (returned exactly once).
type EnqueueOptions ¶
type EnqueueOptions struct {
Environment string
Application string
Source *string
IdempotencyKey string
}
EnqueueOptions carries the optional arguments to Client.Enqueue. Environment defaults to "prod" and Application to "default" when empty. Source is stored only when non-nil. IdempotencyKey, when non-empty, becomes the record id (and the Idempotency-Key on every later Drain send); otherwise a fresh UUID v4 is generated.
type Error ¶
type Error struct {
// Message is the human-readable description of the failure.
Message string
// Err is the underlying cause, if any (e.g. a transport error).
Err error
}
Error is the base error type for all webhookd SDK errors (network failures, etc.).
It wraps an underlying cause when present so callers can use errors.Is/errors.As.
type Event ¶
type Event struct {
Id string `json:"id"`
EventUID string `json:"event_uid"`
EventType string `json:"event_type"`
Application string `json:"application"`
Environment string `json:"environment"`
DeliveriesCreated int `json:"deliveries_created"`
Source *string `json:"source"`
}
Event is the published event, as returned by POST /v1/events (webhookd's EventOut).
type FileStore ¶
type FileStore struct {
// contains filtered or unexported fields
}
FileStore is a durable Store backed by a directory of per-record JSON files, written atomically (temp file + rename) so a crash mid-write never corrupts a record. It survives process restarts — a fresh FileStore over the same directory sees every un-sent record. Suitable for a single process; it does not coordinate concurrent drainers across processes.
func NewFileStore ¶
NewFileStore returns a durable file-backed store rooted at dir, creating it if necessary.
func (*FileStore) ListPending ¶
func (*FileStore) MarkFailed ¶
type ListDeliveriesOptions ¶
type ListDeliveriesOptions struct {
Status string
EndpointID string
EventType string
Since string
Until string
Q string
Offset *int
Limit *int
}
ListDeliveriesOptions carries the optional filters for Client.ListDeliveries. Every filter is forwarded only when provided (non-empty string, or non-nil pointer).
type ListEndpointsOptions ¶
ListEndpointsOptions carries the optional arguments to Client.ListEndpoints. Environment defaults to "prod" when empty; Offset is always sent; Limit is sent only when non-nil.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an in-process, non-durable Store. The default for tests and single-process best-effort buffering.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore returns an empty in-memory store.
func (*MemoryStore) Close ¶
func (s *MemoryStore) Close() error
func (*MemoryStore) ListPending ¶
func (*MemoryStore) MarkFailed ¶
type Option ¶
type Option func(*Client)
Option configures a Client in New.
func WithDrainBatchLimit ¶
WithDrainBatchLimit sets how many records a single Drain pulls from the store (default 100).
func WithHTTPClient ¶
WithHTTPClient injects a custom *http.Client (used for tests, proxies, custom transports).
func WithMaxAttempts ¶
WithMaxAttempts sets the delivery attempts a record gets before Drain parks it dead (default 10).
func WithMaxRetries ¶
WithMaxRetries sets the number of extra attempts for retryable failures (default 2).
func WithOnDead ¶
WithOnDead registers a callback invoked once when Drain parks a record dead (retry budget exhausted). The record passed carries the final attempts count and last error.
func WithStore ¶
WithStore configures the durable outbox store. When set, Enqueue / Drain / StartDrainer become usable; omit it to use only the live Publish.
func WithTimeout ¶
WithTimeout sets the per-request timeout (default 10s). Ignored when WithHTTPClient supplies a client with its own Timeout.
type Page ¶
Page is a single page of a list endpoint — items plus the cursor for the next page (nil at the end).
type Patch ¶
Patch is a raw PATCH mapping for UpdateEndpoint, keyed with the server's snake_case names. PATCH semantics: an OMITTED key is left unchanged; an explicit nil CLEARS the field (JSON null). The map is sent verbatim.
type PublishOptions ¶
type PublishOptions struct {
Environment string
Application string
Source *string
IdempotencyKey string
}
PublishOptions carries the optional arguments to Client.Publish. Environment defaults to "prod" and Application to "default" when empty. Source is sent only when non-nil. IdempotencyKey, when non-empty, is sent as the Idempotency-Key header.
type Record ¶
type Record struct {
ID string `json:"id"`
EventType string `json:"event_type"`
Payload map[string]any `json:"payload"`
Environment string `json:"environment"`
Application string `json:"application"`
Source *string `json:"source"`
CreatedAt time.Time `json:"created_at"`
Attempts int `json:"attempts"`
LastError *string `json:"last_error"`
NextAttemptAt time.Time `json:"next_attempt_at"`
}
Record is one buffered event. ID doubles as the webhookd Idempotency-Key (caller-supplied or a generated UUID v4), so re-saving the same ID (an idempotent enqueue) overwrites, and re-draining after a crash never double-publishes.
type Store ¶
type Store interface {
// Save inserts-or-UPDATEs by record.ID — the same ID overwrites, so enqueue is idempotent.
Save(ctx context.Context, record Record) error
// ListPending returns records not yet sent (and not dead) whose NextAttemptAt <= now, oldest
// first (by CreatedAt), capped at limit.
ListPending(ctx context.Context, limit int) ([]Record, error)
// MarkSent removes (or flags sent) a record after a 2xx.
MarkSent(ctx context.Context, id string) error
// MarkFailed persists the failure and schedules the next retry (or parks the record dead via a
// far-future nextAttemptAt).
MarkFailed(ctx context.Context, id string, lastError *string, attempts int, nextAttemptAt time.Time) error
// Size reports the count of records still buffered (i.e. not yet sent); dead records included.
Size(ctx context.Context) (int, error)
// Close releases any resources (file handles, DB connections). The zero-work stores no-op.
Close() error
}
Store is a durable buffer of pending Records. Every method is safe to call from the background drainer goroutine and the caller goroutine concurrently. Built-ins MemoryStore and FileStore ship in this package (stdlib only); SqliteStore, RedisStore and PostgresStore live in subpackages under go/store so a consumer who never imports them never compiles their drivers.
type Subscription ¶
Subscription is a subscription filter attached to an endpoint. MatchKind is one of exact|prefix|suffix|all.
type VerifyOptions ¶
type VerifyOptions struct {
// Timestamp is the X-Webhook-Timestamp header value (unix seconds). When non-nil the replay
// window is enforced against it — always pass it. When nil, the signature is computed over the
// body alone.
Timestamp *int64
// ToleranceSeconds is the replay window; 0 means the default (300).
ToleranceSeconds int
// Now overrides the current unix time (for tests); 0 means time.Now().Unix().
Now int64
}
VerifyOptions configures Verify.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
webhookd
command
Command webhookd is the official command-line interface for NimbusNexus Webhooks (webhookd).
|
Command webhookd is the official command-line interface for NimbusNexus Webhooks (webhookd). |
|
examples
|
|
|
publish
command
Command publish is a runnable example: publish one event with the SDK.
|
Command publish is a runnable example: publish one event with the SDK. |
|
receiver
command
Command receiver is a runnable net/http server that verifies incoming webhookd webhooks.
|
Command receiver is a runnable net/http server that verifies incoming webhookd webhooks. |
|
store
|
|
|
postgres
Package postgres provides a durable, transactional webhookd.Store backed by PostgreSQL via github.com/jackc/pgx/v5 (pgxpool).
|
Package postgres provides a durable, transactional webhookd.Store backed by PostgreSQL via github.com/jackc/pgx/v5 (pgxpool). |
|
redis
Package redis provides a durable webhookd.Store backed by Redis via github.com/redis/go-redis/v9.
|
Package redis provides a durable webhookd.Store backed by Redis via github.com/redis/go-redis/v9. |
|
sqlite
Package sqlite provides a durable, transactional webhookd.Store backed by SQLite via the pure-Go modernc.org/sqlite driver (no cgo).
|
Package sqlite provides a durable, transactional webhookd.Store backed by SQLite via the pure-Go modernc.org/sqlite driver (no cgo). |
|
storetest
Package storetest provides a shared Store contract test, parametrized over any webhookd.Store implementation.
|
Package storetest provides a shared Store contract test, parametrized over any webhookd.Store implementation. |