Tier: Adapter · Status: Full · Java original:
firefly-callbacks· .NET project:FireflyFramework.Callbacks.{Interfaces,Models,Core,Web,Sdk}
callbacks is the framework's outbound webhook subsystem. Services
publish business events; the dispatcher signs each payload with
HMAC-SHA256, retries with exponential backoff, and records every
attempt to a pluggable Store for audit. A REST admin endpoint
manages targets; an SDK type-safely calls the admin endpoint from
upstream services.
Sub-packages mirror the .NET project split:
| Sub-package | What it provides |
|---|---|
callbacks/interfaces |
DTOs (Target, CallbackEvent, Attempt) + Store, Dispatcher ports |
callbacks/models |
In-memory MemoryStore implementing Store |
callbacks/core |
HMAC-signing Dispatcher with retry, audit-log recording |
callbacks/web |
REST admin handler (CRUD targets, list attempts) |
callbacks/sdk |
Typed client for the admin REST API |
POST <target.URL> with body == event.Payload, plus headers:
| Header | Value |
|---|---|
Content-Type |
application/json |
X-Firefly-Event |
event.Type |
X-Firefly-Event-Id |
event.ID |
X-Firefly-Timestamp |
Unix seconds when the request was sent |
X-Firefly-Signature |
sha256=<hmac-hex> keyed on target.Secret |
X-Correlation-Id |
When event.CorrelationID is set |
| (custom) | Anything from target.Headers |
core.Config{MaxAttempts, InitialDelay} — defaults: 3 attempts,
200 ms initial delay, doubling. Each attempt records an Attempt
audit row regardless of outcome.
type Target struct {
ID, URL, Secret string
EventTypes []string
Headers map[string]string
Active bool
CreatedAt time.Time
}
type CallbackEvent struct {
ID, Type string
Payload []byte
Headers map[string]string
CorrelationID string
}
type Attempt struct {
ID, EventID, TargetID string
Status int
Body, Error string
Attempt int
StartedAt, FinishedAt time.Time
}
type Store interface {
UpsertTarget(ctx, Target) (Target, error)
GetTarget(ctx, id) (Target, error)
ListTargets(ctx) ([]Target, error)
DeleteTarget(ctx, id) error
RecordAttempt(ctx, Attempt) error
ListAttempts(ctx, eventID) ([]Attempt, error)
}
type Dispatcher interface { Dispatch(ctx, CallbackEvent) error }type Config struct {
HTTPClient *http.Client
MaxAttempts int
InitialDelay time.Duration
Now func() time.Time
}
func Default() Config
func NewDispatcher(store cbi.Store, cfg Config) *Dispatcherfunc Handler(store cbi.Store) http.Handler
// Routes:
// GET /callbacks/targets
// POST /callbacks/targets (upsert)
// GET /callbacks/targets/{id}
// DELETE /callbacks/targets/{id}
// GET /callbacks/attempts/{eventId}type Client struct{ ... }
func New(baseURL string) *Client
func (*Client) Targets(ctx) ([]cbi.Target, error)
func (*Client) Upsert(ctx, cbi.Target) (cbi.Target, error)
func (*Client) Delete(ctx, id) errorstore := cbm.NewMemoryStore()
_, _ = store.UpsertTarget(ctx, cbi.Target{
ID: "customers", URL: "https://customer.example.com/cb",
Secret: "shared-secret", Active: true,
EventTypes: []string{"order.placed", "order.shipped"},
})
d := core.NewDispatcher(store, core.Default())
_ = d.Dispatch(ctx, cbi.CallbackEvent{
ID: uuid.New().String(),
Type: "order.placed",
Payload: []byte(`{"id":"o1","customer":"alice"}`),
})cd callbacks
go test ./...Covers HMAC signing, 5xx retry behaviour, event-type filtering, audit trail recording, and the REST admin CRUD.