webhook fan-out
for the beam.
one inbox, many outboxes. toml-defined routes, otp-supervised dispatch, a tiny sqlite ring for replays, and an sse feed you can curl. i wanted webhookd | tee and kept not finding it, so this is that.
# twine.toml [server] port = 8800 db = "twine.db" [[route]] source = "/github" when_header = "X-GitHub-Event: push" destinations = [ "https://hooks.slack.com/services/...", "https://ci.local/trigger", ] [[route]] source = "/stripe" retries = 3 secret_header = "X-Hub-Signature-256" destinations = ["https://ledger.local/stripe"]
curl the sse stream, watch things happen.
$ curl -N http://localhost:8800/_twine/events event: dispatch data: {"id":"01hx...","source":"/github","ok":2,"fail":0,"ms":141} event: dispatch data: {"id":"01hx...","source":"/stripe","ok":1,"fail":0,"ms":63} event: retry data: {"id":"01hx...","dest":"ci.local","attempt":2,"wait_ms":400} $ curl -X POST http://localhost:8800/_twine/replay/01hx... ok
small, and on purpose.
toml routes in, fanout out.
point any service's webhook at /<something>. twine looks up the route, checks an optional header guard, verifies optional hmac, fires the body at every destination concurrently with backoff. retries are per-destination so one slow endpoint doesn't stall the others.
sqlite replay ring.
every event lands in a ~1000-row ring. replay any stored event with one POST /_twine/replay/:id. the db reaps on insert via offset 1000. on a hot server that's a tiny redundant scan. haven't benchmarked.
otp supervised.
each route has its own worker tree. if a dispatch crashes, supervisor restarts the worker. the http listener stays up. this is the part you get for free by writing it in gleam instead of node.
- body size capped at 1mb. bigger payloads get a 413.
- no auth on
/_twine/replay. put it behind something if your twine is public. - the
tailsubcommand is a stub right now. ask me how i know. - 21 tests covering config parsing, routing, sqlite roundtrips, backoff math.
$ git clone https://github.com/f4rkh4d/twine $ cd twine $ gleam run -- check --config twine.toml $ gleam run -- serve --config twine.toml
needs gleam 1.15+ and otp 26+.