Webhook-to-SSE gateway with hierarchical topic routing and signature verification
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.