Don't route it. Don't transform it. FING it.
FING is a JSON event router. Send it any JSON event. It evaluates routes. It transforms and forwards. That's it.
Single binary. No database. No agents. No cloud account. Works anywhere.
-
Copy the example config
cp fing.yaml.example fing.yaml fing validate -
Start FING
fing serve fing serve --config /etc/myapp/fing.yaml -
Send an event
curl -X POST http://localhost:9090/ingest \ -H "Content-Type: application/json" \ -d '{"action":"opened","pull_request":{"title":"Fix thing"},"repository":{"name":"myrepo"}}' -
Or pipe
cat events.ndjson | fing serve
One YAML file. Lives in your repo. Ships with your code.
destinations:
slack:
type: webhook
url: https://hooks.slack.com/services/...
headers:
Authorization: Bearer ${SLACK_TOKEN}
routes:
- name: pr-notify
filter: '.action == "opened"'
transform: |
{
text: ("PR opened: " + .pull_request.title),
repo: .repository.name
}
to:
- slack
- stdoutAll jq forms work in filter and transform. If you can express it in jq, FING handles it. If you can't, write a service.
Route behavior:
filter— jq expression returning truthy/falsy. Omit to match all events.transform— jq expression returning the outbound object. Omit to forward raw input.to— list of destinations.stdoutis always available, no declaration required.- Fan-out: all matching routes fire for each event.
POST /ingest — Send an event
GET /health — Health check
GET /routes — List routes + destination queue depths
POST /reload — Hot-reload config
kill -HUP <pid> or curl -X POST http://localhost:9090/reload
SIGTERM/SIGINT — drains in-flight deliveries, exits 0.
Webhooks defined once, referenced by name. stdout built-in.
4xx responses dropped. 5xx retried with exponential backoff.
Environment variables interpolated in url and headers: ${MY_VAR}.
# coming soon
brew install zuchka/tap/fing
curl -sf https://start.fing.ing | sh
MIT license · fing.ing