beam

How it works

The request path from provider to localhost — Worker, Durable Object, WebSocket, CLI.

beam is three moving parts: a Worker that routes and authenticates, a Durable Object that holds your WebSocket, and the CLI that replays deliveries to localhost. A Durable Object is the one primitive this problem needs: a single, globally-addressable, stateful coordination point per name.

The request path

sequenceDiagram
    participant S as Sender<br/>(GitHub/Stripe)
    participant W as Worker
    participant DO as DurableObject("name")
    participant C as beam CLI
    participant L as localhost:3000

    C->>W: GET /webhook/name  (WS upgrade + Bearer token)
    W->>W: constant-time check vs API_TOKEN secret
    W->>DO: route by idFromName("name")
    DO-->>C: 101 Switching Protocols + {type:"connected"}

    Note over S,L: …later, a webhook arrives…
    S->>W: POST /webhook/name/orders?id=7
    W->>DO: forward (delivery path — no auth)
    DO-->>C: {type:"webhook", method, path, query, headers, body} over WS
    DO-->>S: 202 Accepted   (fire-and-forget)
    C->>L: replay POST /orders?id=7

idFromName("orders") always resolves to the same Durable Object instance, so the DO that holds the WebSocket is exactly the one a delivery routes to — no shared store, no pub/sub, no registry.

The pieces

flowchart TB
    subgraph clients["Senders & operator"]
        ext["webhook providers<br/>(GitHub · Stripe · Linear)"]
        cli["beam CLI<br/>(kong + gorilla/ws)"]
    end
    subgraph edge["Cloudflare edge"]
        w["Worker<br/>route · auth · forward"]
        do["DurableObject &quot;name&quot;<br/>holds the WebSocket"]
    end
    local["localhost:3000<br/>(your app)"]

    ext -->|POST /webhook/name| w
    cli -->|WS upgrade + Bearer| w
    w -->|idFromName| do
    do <-->|JSON frames| cli
    cli -->|replay| local

Worker

Routes by path, runs the constant-time token check on the listen side, and forwards deliveries to the right Durable Object. Auth happens here, before durable state is touched.

Durable Object (WebhookHub)

Holds your CLI's WebSocket and pushes each delivery to it as a JSON frame. It uses Cloudflare's hibernation APIacceptWebSocket plus a ping/pong auto-response pair — so an idle tunnel isn't pinned in memory and reconnects survive DO eviction.

CLI

Connects, presents the token, and replays each frame to your forward target (or prints it with --tail). Auto-reconnect with backoff, keepalive pings, and clean SIGINT shutdown make it survive flaky networks.

The delivery envelope

Bodies ride inside a JSON frame as base64, so binary payloads survive the WebSocket transport and the CLI replays them verbatim. The frame carries { type, method, path, query, headers, body }. The WebSocket frame cap is ~1 MiB, which bounds payload size — see Not in scope.

For the reasoning behind these choices, see Design notes.

On this page