Redline

Behind the Scenes

How Redline is actually built

Not a marketing page — an honest walkthrough of the architecture, the API, the database, and how the security model actually works.

Overview

Four tools, one job.

Redline is a handful of free, no-signup developer tools sharing one Next.js app: an API Tester for firing one-off HTTP requests, a Webhook Debugger for watching real webhook traffic land in a disposable inbox, a JWT Decoder, and a Timestamp Converter. None of them require an account — there's no auth system, no user table. Access to a webhook inbox is simply knowing its (unguessable) URL; the JWT Decoder and Timestamp Converter don't touch the server at all.

The app was designed with an optional-module architecture from the start — pieces like sign-in or analytics can be switched on later without dragging in code or dependencies for the ones nobody's using. Right now, the only one turned on is the database, because the Webhook Debugger needs somewhere durable to keep captured requests.

Tech stack

What's actually running.

LayerChoiceWhy
Next.js 16React 19, App Router, TurbopackServer + Client Components, one deploy
HonoTyped API routerMounted under /api via a single catch-all route
SupabaseManaged PostgresPersists webhook inbox data across requests
Tailwind v4CSS-first configEntire theme lives in one CSS file, no separate config
shadcn/ui + Base UICopied-in componentsEditable source, not an opaque dependency
PlaywrightEnd-to-end testsRuns against a real production build — see Testing

Repository map

Where things live.

app/
  page.tsx                  # landing page
  tools/page.tsx            # hub page — a card per tool
  tester/page.tsx           # API Tester
  webhooks/page.tsx         # create a new inbox
  webhooks/[id]/page.tsx    # a single inbox
  jwt/page.tsx              # JWT Decoder (fully client-side)
  timestamp/page.tsx        # Timestamp Converter (fully client-side)
  api/[[...route]]/route.ts # hands every /api/* request to Hono

server/
  hono/app.ts               # composes all API routes
  hono/routes/               # proxy.ts, webhooks.ts, hook.ts
  hono/lib/ssrf-guard.ts    # blocks the proxy from hitting private IPs
  db/admin.ts                # the only client allowed to touch the database

components/
  ui/                        # shadcn primitives
  sections/                  # landing-page sections
  tester/, webhooks/, jwt/, timestamp/  # each tool's own pieces

lib/
  types.ts                   # shared request/response contracts
  relative-time.ts           # bidirectional "in 3 hours" / "2 days ago"

Pages & routes

Every route, and what owns it.

RoutePurpose
/Landing page
/toolsHub page — a card for each tool
/testerAPI Tester
/webhooksCreate a new webhook inbox
/webhooks/:idA single inbox — ingest URL + live capture
/jwtJWT Decoder — fully client-side, nothing sent to a server
/timestampTimestamp Converter — also fully client-side
/aboutProduct story & principles
/contactWays to reach us
/privacy, /terms, /disclaimer, /cookies, /securityLegal pages
/api/*Every backend endpoint — see API reference

How the API Tester works

The round trip, from click to response panel.

  1. You fill in method, URL, headers, and body in the browser, then hit Send.
  2. Your browser posts that as JSON to /api/proxy— never straight to the target API. That's what lets Redline bypass CORS entirely: the request happens server-side, not in your browser.
  3. The server validates the target URL (see Security model), then makes the real request with a 20-second timeout.
  4. Status, headers, body, timing, and size come back as one JSON object and render in the response panel, color-coded by status class.
  5. The sent request is saved to your browser's localStorage, capped at 15 — the only place tester history lives. Redline's server never stores your requests or responses.

How the Webhook Debugger works

The part that actually needs a database.

  1. Creating an inbox inserts one row into a webhook_bins table and returns its random ID.
  2. You get an ingest URL — /api/hook/:id — and hand it to whatever should send you webhooks.
  3. Any request to that URL, any method, any path, is captured in full: method, path, query string, every header, the raw body, and the sender's IP — stored against that inbox's ID. There's no auth on this endpoint by design; the inbox ID is the access control.
  4. The inbox page polls for new requests every few seconds and shows them newest-first, with a detail view for headers, query params, and body.
  5. Deleting an inbox cascades to every request captured in it, at the database level.

API reference

Everything under /api.

MethodPathDoes
GET/api/healthLiveness check
POST/api/proxyPerforms the API Tester's request, SSRF-guarded
POST/api/webhooksCreates an inbox
GET/api/webhooks/:idInbox metadata, or 404
GET/api/webhooks/:id/requestsUp to 200 captured requests, newest first
DELETE/api/webhooks/:id/requestsClears captured requests
DELETE/api/webhooks/:idDeletes the inbox
ALL/api/hook/:idThe public ingest endpoint

Database & row-level security

Two tables, locked down by default.

webhook_bins
  id uuid primary key
  name text
  created_at, last_active_at timestamptz

webhook_requests
  id uuid primary key
  bin_id uuid references webhook_bins(id) on delete cascade
  method text, path text, query jsonb, headers jsonb
  body text, content_type text, ip text
  received_at timestamptz

Both tables have row-level security enabled with zero policies defined. That's deliberate: the public/anonymous database credential has no access to this data at all, full stop. Every read and write goes through a single privileged server-side client that bypasses RLS — there's only one trusted caller, so there was never a need for granular policies.

Security model

The mechanisms actually doing the work.

SSRF guard on the proxy

Every API Tester target is checked before the request fires: localhost, private and link-local IPs (including the cloud metadata address), and any hostname that resolves to one, are rejected — checked via a real DNS lookup, not just a string match.

The database credential never reaches your browser

It's excluded from client bundles at build time, not just by convention — importing it from browser-facing code fails the build outright.

An inbox's URL is its access control

No accounts means no per-user permission check — an inbox is protected the way a well-known tool like webhook.site protects one: a long, random, unguessable ID. Whoever has the link can see it.

Captured data can be sensitive

A webhook payload is stored exactly as received, including any auth headers a third party sent. Clear or delete an inbox once you're done with it — see the Security Policy.

Design system

One theme file, no separate config.

Three full color scales — scarlet, orange, and a warm neutral grey — are mapped onto semantic tokens (primary, background, destructive, and so on), plus two custom tokens for status-code coloring across both tools.

scarlet-rush-500

scarlet-rush-600

orange-500

shadow-grey-950

shadow-grey-800

shadow-grey-50

Type is three faces: a sans for body and UI, a monospace face used heavily for anything technical (URLs, headers, JSON), and a display serif reserved for statement headings rather than every heading. Every component under components/ui/ is source in the repo, not an installed package — added via a CLI, then owned and editable directly.

Testing

37 end-to-end tests, run against a real production build.

Every page and both tools are covered by Playwright tests that run against an actual production build — not mocked, not a dev server. That includes a realproxied HTTP request in the API Tester tests, and a full create → capture → inspect → delete round trip against the real database for the Webhook Debugger. The database-dependent tests check that real credentials are configured before running, and skip cleanly with a clear reason if they aren't, rather than reporting a false failure.