The HTTP edge is how Binions publishes a service so the outside world can reach it. Your daemons and backends listen on loopback, behind the firewall; a single front door — the platform’s Traefik edge — terminates TLS and forwards public traffic to them. You control that front door from a playbook with the traefik. verbs, and the same handful of verbs publishes plain web pages, WebSocket streams, Server-Sent Events, and gRPC backends alike — complete with the guards that decide who gets through.
Good to know. This is the integration view — what the edge does and how to drive it from a playbook. For the full daemon reference (config, health, every event), see the Traefik linker service. The edge itself is part of the platform’s supporting infrastructure.
Everything Binions runs lives on one host and talks over localhost. That keeps the platform private and fast, but it also means nothing is reachable from the internet by default — which is exactly what you want until you decide to expose something. Publishing a service is a deliberate, declarative act:
traefik.register_route. A single protocol: field on the route tells the edge what kind of traffic it carries, so the same verb publishes a web page, a live socket, or a gRPC service.middlewares: list in the very step that publishes it.The Traefik linker is a control plane: it writes the edge’s configuration when a playbook asks it to. It does not sit in the request path — live traffic flows straight through Traefik to your backend, never through the linker. That separation is why a route survives even if the linker is idle.
Every route carries a protocol: field. It defaults to https, so a route written without one behaves like an ordinary TLS web route. Set it explicitly to publish realtime or RPC traffic, and the edge applies the right handling automatically — long idle timeouts for sockets, unbuffered streaming for events, HTTP/2 to gRPC backends.
protocol: | What it publishes | What the edge does for you |
|---|---|---|
| http | Plain HTTP (cleartext) | Routes on the web entry point; no TLS. |
| https (default) | HTTPS web traffic | Terminates TLS at the edge; backend stays plain HTTP. |
| ws | WebSocket over cleartext | Auto-detects the Connection: Upgrade handshake; raises the idle timeout so long-lived sockets stay open. |
| wss | WebSocket over TLS | As ws, plus TLS termination at the edge. |
| sse | Server-Sent Events | Disables response buffering so events reach the browser the instant they are emitted. |
| grpc | A gRPC backend | Speaks HTTP/2 to the backend; clients use gRPC-over-TLS at the edge. |
| grpc-web | A gRPC backend for browsers | Translates gRPC-Web requests to gRPC before forwarding, so browser clients can call a gRPC service directly. |
The big idea. You describe the route — host, backend, protocol, guards — and the edge works out the proxy details. The protocol-specific handling is wired up for you, and no certificate ever touches your backend.
Four verbs cover the whole lifecycle of an edge route. Use them as playbook steps with the traefik. prefix.
| Verb | What it does |
|---|---|
traefik.register_route | Create or replace a route. Writes the route into the edge’s dynamic configuration and emits Fact.Traefik.RouteRegistered; a spec that fails validation is rejected with a loud Fact.Traefik.RouteRegisterFailed instead. |
traefik.unregister_route | Remove a route by name. Emits Fact.Traefik.RouteUnregistered. |
traefik.list_routes | Report the current routes — both what Binions has written and what the live edge is actually serving — as Fact.Traefik.Routes. |
traefik.reload | An acknowledgement only. The edge watches its configuration directory and reloads on its own, so this verb simply emits Fact.Traefik.Reconfigured and does no work. You almost never need it. |
The route is one object: its fields nest under a single route: key inside the step’s with:. The examples below show the exact shape. The fields:
| name | Required. The route’s identifier — lower-case letters, digits, and hyphens ([a-z0-9-]). Re-using a name replaces that route. |
| host | Required. The host name to match, e.g. app.example.com. |
| backend | The internal service URL the edge forwards to — an http:// or https:// address, typically a loopback port. Give either this or backends. |
| backends | A list of url/weight entries instead of the single backend — the edge balances traffic across them by weight (weight defaults to 1). |
| path_prefix | Optional. Narrows the match: the route applies only to paths under this prefix, ANDed with the host rule. |
| strip_prefix | Optional. Strips the matched path_prefix from the request before it is forwarded, so the backend sees clean paths. |
| protocol | Optional. One of the seven values above; defaults to https. |
| middlewares | Optional. An ordered list of edge guards applied to every request on the route — see the guards section below. |
| entry_points | Optional. Which public ports the route attaches to; defaults to both the web and the secure web entry points. |
| tls.cert_resolver | Optional. The name of a certificate resolver (such as letsencrypt) so the edge obtains and renews the certificate for the host. |
The most common job: take an internal daemon’s web server and give it a public host name with a certificate. Here the Showman page server, listening on a loopback port, is published at pages.example.com. Routes are usually registered once, at boot, in a provisioning playbook — so they re-apply every time the platform starts.
name: publish-showman-pages
description: Expose the Showman page server at the HTTP edge.
trigger:
event: Fact.System.Boot
filter:
component.eq: playbook-service
steps:
- id: register_pages_route
run: traefik.register_route
with:
route:
name: showman-pages
host: pages.example.com
backend: http://127.0.0.1:9220
protocol: https
entry_points: [websecure]
tls:
cert_resolver: letsencrypt
On boot the linker writes the route, the edge picks it up and requests a certificate for pages.example.com, and a Fact.Traefik.RouteRegistered records the result. Your backend keeps speaking plain HTTP on loopback — the edge does the TLS.
A route can carry its own protection. The optional middlewares: list declares edge guards that Traefik applies to every request before it reaches your backend — so the playbook that publishes a service is also the record of who may call it. Each entry is a flat object selected by a type: field:
type: | What it does | Fields |
|---|---|---|
| ip_allowlist | Only the listed sources reach the backend; everyone else is turned away at the edge. | source_ranges — a list of IP addresses or CIDR blocks. |
| rate_limit | Caps the request rate the route will accept. | average requests per period, optional burst (defaults to average) and period_secs (defaults to 1). |
| basic_auth | A username/password challenge at the edge — the backend never sees unauthenticated traffic. | users — htpasswd-format entries (user:$apr1$…). Interpolate ${secret.*} in the provisioning playbook so credentials never sit in a playbook file. |
| forward_auth | Delegates the allow/deny decision to an external authentication service on every request. | address, plus optional auth_response_headers and trust_forward_header. |
| headers | Sets custom request and response headers. | request and response maps of header names to values. |
Validation is deliberately loud. The middleware set is closed and every field is checked at registration: a misspelled or foreign field, a malformed CIDR or htpasswd entry, a rate limit missing its average — each rejects the route with Fact.Traefik.RouteRegisterFailed instead of publishing it half-guarded.
Ordering is predictable: your middlewares chain in the order you declare them, and always ahead of anything the edge adds on its own — the path-prefix strip and the protocol-specific handling (SSE headers, gRPC-Web translation) come after your chain. Each declared guard materializes as its own named middleware in the edge’s dynamic configuration, prefixed with the route’s name, so the running config reads back exactly as the playbook wrote it.
A typical internet-facing entry — office networks only, throttled to ten requests a second:
name: edge-route-ip-allowlist
description: Publish the hooks entry - office IPs only, 10 requests per second.
trigger:
event: Fact.System.Boot
steps:
- id: route
run: traefik.register_route
with:
route:
name: hooks-in
host: "hooks.example.com"
backend: "http://127.0.0.1:9200"
protocol: https
middlewares:
- type: ip_allowlist
source_ranges: ["203.0.113.0/24", "10.0.0.0/8"]
- type: rate_limit
average: 10
burst: 20
Routes match a host; they can also match a slice of one. Add path_prefix: and the rule narrows to that prefix, ANDed with the host match — so api.example.com/v2 can go to one backend while the rest of the host goes elsewhere. Set strip_prefix as well and the edge removes the prefix before forwarding, so the backend serves clean paths without knowing where it is mounted.
A route can also spread its traffic. Declare backends: — a list of url/weight pairs — instead of the single backend, and the edge builds a weighted service across them: nine parts to one for a cautious canary, equal weights for plain balancing.
name: publish-api-canary
description: Route /v2 on the API host, split 90/10 across two backends.
trigger:
event: Fact.System.Boot
filter:
component.eq: playbook-service
steps:
- id: register_canary_route
run: traefik.register_route
with:
route:
name: api-v2-canary
host: api.example.com
path_prefix: /v2
backends:
- url: "http://127.0.0.1:9310"
weight: 9
- url: "http://127.0.0.1:9311"
weight: 1
A live dashboard, a notifications feed, or a streaming API needs a connection that stays open. Set protocol: wss and the edge detects the WebSocket upgrade and keeps the socket alive through long idle periods — the protocol handling comes with the route.
name: publish-live-dashboard
description: Expose a streaming dashboard backend over WSS.
trigger:
event: Fact.System.Boot
filter:
component.eq: playbook-service
steps:
- id: register_dashboard_socket
run: traefik.register_route
with:
route:
name: dashboard-ws
host: dashboard.example.com
backend: http://127.0.0.1:9230
protocol: wss
tls:
cert_resolver: letsencrypt
Browser clients now connect to wss://dashboard.example.com; the edge terminates TLS and proxies a plain WebSocket to the backend daemon on loopback. The same pattern, with protocol: sse, publishes a Server-Sent Events feed; with protocol: grpc or grpc-web, a gRPC service.
The edge is the only public surface. Backends stay on loopback and are never exposed directly; the firewall blocks everything but the edge. A route is the single, auditable way a service becomes reachable — its guards travel with it — and removing the route with
traefik.unregister_routetakes it offline again.
/in gatewayThe platform accepts HTTP from the outside world directly into playbooks. Any request to /in/<route> on the admin edge (port 8446, Basic-Auth protected) is turned into a Fact.Http.Received event carrying the route, method, query, selected headers, body and a correlation id. There is nothing to register: a playbook whose trigger filters on the route is the endpoint — exactly like a webhook node in other automation tools, but with no node to configure.
name: orders-count
trigger:
event: Fact.Http.Received
filter: { route.eq: orders-count }
steps:
- run: database.count
with: { table: orders }
- run: webhook.send # final step = the synchronous reply
with:
url: "http://127.0.0.1:9099/in/_reply/${trigger.correlation_id}"
body: { count: "${prev.count}" }
The gateway holds the HTTP request open until that final reply step lands (10 s by default), so the caller gets a real synchronous answer — a browser button can fetch() /in/orders-count and render the JSON. Add ?async=1 (or let the timeout pass) and the caller gets an immediate 202 with the correlation id instead. Every exchange is audited with a Fact.Http.Replied event. The reply URL is loopback-guarded — only the platform itself can complete a pending request.
Edge guards decide who can reach the gateway; a signature proves who sent the request. For senders that sign their webhooks — payment providers, Git hosting, most SaaS event feeds — the gateway verifies an HMAC-SHA256 signature of the raw request body before the request is allowed to become an event. It is switched on per route in the showman daemon’s configuration ([http_in.hmac.<route>]):
[http_in.hmac.stripe-events]
secret_file = "/etc/binions/secrets/showman/stripe-webhook.secret"
header = "stripe-signature" # matched lowercase; default "x-signature"
prefix = "sha256=" # optional prefix stripped from the header value
algo = "sha256" # only sha256 is supported
prefix.401 and audited as Fact.Http.Rejected with a reason of missing_signature, bad_signature or hmac_misconfig. No Fact.Http.Received is emitted, so no automation ever runs on an unverified body — and since Fact.Http.Rejected is itself a trigger event, a playbook can watch it and alert on repeated bad signatures.The two layers are complementary, not alternatives: middlewares on the Traefik route (an IP allow-list, a rate limit, Basic-Auth) control who can reach /in at all, while the body signature authenticates each individual request end-to-end. For an internet-facing webhook endpoint, use both.
The same daemon that serves your pages now keeps them live. Three endpoints, no channel registration needed:
GET /ws/<channel> — a browser opens a WebSocket; everything pushed to that channel fans out to every connected client. Frames the browser sends come back into the platform as Fact.Showman.WsMessage events — so a click in the page can trigger a playbook.GET /sse/<channel> — the same channel over Server-Sent Events, for clients that prefer plain HTTP streaming.POST /in/_push/<channel> — loopback-only ingest: a playbook step (webhook.send) is the live update.Mark a page as live when you publish it (show.register_page with live: true and a channel:), and the full loop — browser click → event → database write → scheduled tick → push → screen — runs with three small playbooks and zero custom server code. One caution for live page JavaScript: avoid template literals — ${…} belongs to the playbook interpolator.
Fact.System.Boot, so your public surface is declared in one place and re-applied on every start.name is the handle you pass to unregister_route and the label you see in list_routes; a clear name (dashboard-ws, showman-pages) keeps the edge readable.http://127.0.0.1:<port> and let the edge own TLS and the public host. Your backend never needs a certificate of its own.middlewares: entry lives in the same playbook step as the route itself — one place to review both what is exposed and how it is protected./in gateway’s per-route HMAC check authenticates every body. They stop different failures — use both for anything that faces the internet.list_routes. Because it reports both the written config and the routers the edge is actually serving, it is the quickest way to confirm a route took effect — or to spot one that was registered but never reconciled.The edge follows the same pattern as every Binions integration: one broker, configured by a small, generic verb set, selected by a field. Just as the webhook caller speaks to any HTTP API and the data transporter speaks to any S3-compatible store, the Traefik linker fronts any backend — and the protocol: field is the one selector that decides what kind of traffic the route carries. There are no per-application route types to learn; there is one route, shaped by one field, with its guards declared right beside it. See the integration model for the principle, and the integration catalog for the full set of brokers.
The edge is also where Binions offers itself to an AI assistant: the showman daemon serves a built-in Model Context Protocol server at POST /mcp, exposing selected playbooks as callable tools. It is an inbound edge endpoint like the others, behind the same authenticated entry point. See MCP server.