Use case
Webhook & event processing
A webhook endpoint is a small pipeline pretending to be a controller action. bloccs makes the pipeline explicit: one node owns the HTTP edge, a pure node verifies the payload, a split decides where each event goes, and the effectful sinks each declare exactly what they touch. Unknown events are dead-lettered instead of dropped, and every hop is typed and traced — so a malformed payload fails at the boundary, not three stages deep.
The flow
Where this stands today
Where this stands today: verification, routing, persistence, dispatch and dead-lettering all run in 0.8. By design, bloccs doesn't serve HTTP itself — receive is fed by your web layer (Phoenix or any endpoint), and everything from there down is bloccs.
Source of truth
The manifest
The whole network is one TOML file. Drop it in, run mix bloccs.compile, and bloccs emits a
Broadway supervision tree from it — the compiler checks every edge, schema, and
declared effect first.
- Edges match ports by schema, or it won't compile.
- Effects are declared per node — nothing touches the outside world undeclared.
- Supervision and concurrency are part of the file, not an afterthought.
[network]
id = "webhooks"
version = "0.1.0"
runtime = "beam"
[nodes]
receive = { use = "nodes/receive.bloccs" }
verify = { use = "nodes/verify.bloccs" }
route = { use = "nodes/route.bloccs" }
persist = { use = "nodes/persist.bloccs" }
dispatch = { use = "nodes/dispatch.bloccs" }
dlq = { use = "nodes/deadletter.bloccs" }
[[edges]]
from = "receive.received"
to = "verify.webhook"
[[edges]]
from = "verify.valid"
to = "route.webhook"
# Fan-out: a known event is both persisted and dispatched downstream.
[[edges]]
from = "route.known"
to = ["persist.event", "dispatch.event"]
[[edges]]
from = "route.unknown"
to = "dlq.event"
[expose]
in = { hook = "receive.received" }
out = { stored = "persist.stored", sent = "dispatch.sent", dead = "dlq.recorded" }
[supervision]
strategy = "rest_for_one"
max_restarts = 5
max_seconds = 60
[deploy]
concurrency = { persist = 1, dispatch = 4 }
Anatomy
Node by node
Each node declares its kind and the capabilities it's allowed to use. Pure nodes touch nothing; effectful nodes carry a badge for exactly what they reach.
receive
Source +HTTPAccepts the inbound POST and emits a raw Webhook@1. The only node allowed to touch the network edge.
verify
NodeChecks the signature and shape. Pure: same input, same verdict, no I/O — trivially testable.
route
SplitBranches on event type. Known types continue; everything else is forced down the dead-letter path.
persist
Sink +DBWrites the event to the store. Declares the DB capability — a stray HTTP call here won't compile.
dispatch
Sink +HTTPFans the known event downstream. Runs concurrently with persist under the supervisor.
dlq
SinkRecords unknowns instead of dropping them, so nothing silently disappears.