Webhook-to-SSE gateway with hierarchical topic routing and signature verification
at main 114 lines 3.3 kB view raw view rendered
1# wicket 2 3A webhook-to-SSE gateway. Receives webhook POSTs, delivers them to 4subscribers as Server-Sent Events. 5 6## How it works 7 8The entire HTTP API is two verbs on the same path: 9 10``` 11POST /<path> → publish an event 12GET /<path> Accept: text/event-stream → subscribe (SSE stream) 13``` 14 15The path is an arbitrary topic name. Path separators create a hierarchy — 16subscribing to a prefix gets you all events underneath it: 17 18``` 19GET /github.com/chrisguidry/docketeer → just docketeer events 20GET /github.com/chrisguidry/ → all chrisguidry repo events 21GET /github.com/ → all GitHub events 22GET / → everything 23``` 24 25A POST to `/github.com/chrisguidry/docketeer` delivers to all four 26subscribers above. 27 28## Zero configuration by default 29 30wicket works out of the box with no configuration — any path accepts 31POSTs and SSE connections. A YAML configuration file layers in authentication 32only where needed: 33 34```yaml 35paths: 36 github.com/chrisguidry/docketeer: 37 verify: hmac-sha256 38 secret: "the-github-webhook-secret" 39 signature_header: X-Hub-Signature-256 40 subscribe_secret: "token-for-docketeer-subscribers" 41``` 42 43Values support environment variable interpolation via `$VAR` or `${VAR}` 44syntax, so secrets can come from the environment rather than being hardcoded: 45 46```yaml 47paths: 48 github.com/chrisguidry/docketeer: 49 verify: hmac-sha256 50 secret: "${WEBHOOK_SECRET}" 51 signature_header: X-Hub-Signature-256 52 subscribe_secret: "${SUBSCRIBE_TOKEN}" 53``` 54 55- Unconfigured paths accept POSTs without verification 56- Configured paths verify webhook signatures and reject invalid ones 57- Subscribe secrets require `Authorization: Bearer <token>` for SSE connections 58- Configuration is hot-reloaded on file change or SIGHUP 59 60## Browser support 61 62CORS headers are sent on all responses, so browser apps can POST events 63and subscribe via `EventSource`. One limitation: the browser's 64`EventSource` API doesn't support custom headers, so browser clients 65can only subscribe to paths without a `subscribe_secret`. Protected 66paths are for server-to-server use. 67 68## Event format 69 70Every event is wrapped in an envelope and delivered as SSE: 71 72``` 73id: unique-event-id 74data: {"id":"...","timestamp":"...","path":"github.com/chrisguidry/docketeer","headers":{...},"payload":{...}} 75``` 76 77Subscribers can reconnect with `Last-Event-ID` to replay missed events 78from an in-memory ring buffer (default 1000 events). 79 80## Filtering 81 82Optional query params filter events on the subscribe side: 83 84``` 85GET /github.com/chrisguidry/docketeer?filter=payload.ref:refs/heads/main 86``` 87 88Simple dot-path equality matching. Multiple filters are AND'd. 89 90## Usage 91 92```bash 93wicket # listen on :8080, no configuration 94wicket -address :9090 # custom listen address 95wicket -configuration wicket.yaml # with webhook verification 96wicket -buffer-size 5000 # larger replay buffer 97``` 98 99## Building 100 101```bash 102go build -o wicket . 103``` 104 105Or with Docker: 106 107```bash 108docker build -t wicket . 109``` 110 111## Why the name "wicket"? 112 113A "wicket" is a [small door or gate](https://en.wiktionary.org/wiki/wicket) 114next to a larger one.