Dev Tunnel
Receive public webhooks and WebSockets on your local dev server without deploying — through a self-hosted reverse tunnel relay. Pure Bun, no third-party tunnel service.
#Why a Tunnel
Providers like Twilio, Stripe, GitHub, and OAuth callbacks need a public HTTPS URL to reach your code. Your dev machine is behind NAT, so the choice is usually redeploy-on-every-change or a third-party tunnel service. AbsoluteJS gives you a third option you fully own.
# Your laptop is behind NAT — the internet can't reach it.
# Webhook providers (Twilio, Stripe, GitHub, OAuth callbacks) need a
# PUBLIC https URL to POST to. The usual options:
#
# 1. Deploy on every change -> slow feedback loop
# 2. A third-party tunnel service -> another dependency + account
#
# AbsoluteJS ships its own reverse tunnel instead: you run a tiny relay
# on any always-on host with a public URL, and 'bun run dev' dials OUT to
# it. The relay forwards public traffic back down that connection to your
# local server. Pure Bun, no third-party tunnel service.#How It Works
You run a small relay on any always-on host with a public URL. Your dev server dials out to the relay over a single token-gated control WebSocket; the relay replays public requests down that socket and streams responses back. WebSocket connections are multiplexed over the same control socket by id.
bun run dev → localhost:3000#Deploy the Relay
The relay is a one-line app. Deploy it once to any host with a public URL (DigitalOcean App Platform shown):
# 1. Put the relay template in its own git repo. It's a one-line app:
# package.json -> "start": "absolute tunnel-relay"
# 2. Pick a long random shared secret (the dev client must present it):
openssl rand -hex 32
# 3. Deploy to any always-on host with a public URL. On DigitalOcean
# App Platform (Docker source, http_port 8080):
doctl apps create --spec .do/app.yaml
# 4. After the first deploy you get a URL like
# https://my-relay-xxxxx.ondigitalocean.app — set it as
# ABSOLUTE_TUNNEL_PUBLIC_URL and redeploy.#The Relay Command
absolute tunnel-relay is a built-in CLI command — there is no app code to write. It reads its configuration from the environment:
# The relay is a built-in CLI command — no app code to write.
absolute tunnel-relay
# It reads three env vars:
# PORT (the public port; App Platform injects it)
# ABSOLUTE_TUNNEL_TOKEN (shared secret; the dev client must match)
# ABSOLUTE_TUNNEL_PUBLIC_URL (the relay's own public URL, optional)
#
# Single-tenant per token: one dev machine at a time. Give each developer
# their own relay (or token) if you need several.#Point Your Dev Server At It
In the consuming app, configure the tunnel under dev in absolute.config.ts:
// absolute.config.ts — point your dev server at the relay.
import { defineConfig } from '@absolutejs/absolute';
export default defineConfig({
dev: {
tunnel: {
relay: 'https://my-relay-xxxxx.ondigitalocean.app',
token: process.env.ABSOLUTE_TUNNEL_TOKEN // same secret as the relay
}
}
});Or configure it entirely from the environment:
# Or configure it entirely from the environment (no config edits):
ABSOLUTE_TUNNEL_RELAY=https://my-relay-xxxxx.ondigitalocean.app
ABSOLUTE_TUNNEL_TOKEN=<the same long random secret the relay uses>#Running Dev
When a tunnel is configured, bun run dev prints a Public: URL alongside the local one. It routes straight to your local server — use it as any webhook provider's callback URL and every request hits your live code with full HMR, breakpoints, and logs:
$ bun run devABSOLUTEJS v0.19.0 dev ➜ Local: http://localhost:3000/ ➜ Public: https://my-relay-xxxxx.ondigitalocean.app/
#Webhook Example
Point any provider's callback at the public URL. Both HTTP webhooks and provider WebSockets (such as Twilio Media Streams) flow through to your local handlers:
# Example: Twilio voice + SMS webhooks during local development.
# Point your number's webhooks at the Public URL the dev server printed:
# Voice A CALL COMES IN -> https://my-relay-xxxxx.ondigitalocean.app/v1/voice/phone/twiml
# Messaging A MESSAGE COMES IN -> https://my-relay-xxxxx.ondigitalocean.app/v1/sms/intake
# Twilio's Media Streams (WSS) work too — the relay multiplexes the
# WebSocket back to your local server over the same control connection.
# Edit your handler, save, and the next call hits the new code instantly.#Security
The relay is gated by a shared token and is for development ingress only:
// The relay is gated by the shared token. Treat it like a credential:
// - Generate it with 'openssl rand -hex 32' (don't reuse).
// - Keep it in .env, never commit it.
// - Rotate it by changing both the relay env var and the dev client.
//
// The relay forwards EVERY public request to your local machine while
// 'bun run dev' is connected, so only run it during active development and
// stop the dev server when you're done. The relay is for dev ingress only —
// production traffic should hit your deployed app directly.