gettoolbox
Blog / Redis and WebhookInspector
rediswebhooksinfrastructure

Redis and WebhookInspector

A look at what Redis is, and why it made sense to use it as the storage layer for WebhookInspector.


What Redis is

Redis is an in-memory data store. You run it as a server, and clients connect to read and write data over TCP. By default, everything lives in RAM — which makes reads and writes very fast, but also means data is gone when the process stops (unless you configure persistence).

What makes Redis more useful than a plain cache is its data structures. You don’t just store strings — you can store lists, sets, sorted sets, hashes, and a few other types. Each comes with its own set of operations. A list, for example, supports LPUSH to prepend elements, LRANGE to read a slice, and LTRIM to cap the list at a fixed length. These aren’t just convenience — they’re atomic, so you don’t need transactions for common patterns.

Redis also has TTLs built in at the key level. You set EXPIRE key 86400 and the key disappears after 24 hours without any background job or cleanup query.

The constraint: serverless

GetToolbox runs on Vercel. Each request is handled by a serverless function that spins up, runs, and exits. There’s no persistent process, no shared memory between requests, and no long-lived TCP connections.

This rules out SQLite (no shared filesystem) and makes a traditional Postgres connection pool awkward — you’d be opening and closing a connection on every request. It’s not impossible, but it adds latency and eats into connection limits quickly.

Redis has the same TCP connection problem, but Upstash offers a Redis-compatible API over HTTP. Each operation is an HTTPS request to their servers. No connection to manage, no pool to configure. It fits the serverless model without any glue.

What WebhookInspector actually stores

When you open WebhookInspector, you get a unique endpoint URL — something like /api/hook/abc123. Any HTTP request sent to that URL gets recorded and shown in the browser in real time.

There are two things stored per endpoint:

Events — a Redis list at hook:{id}:events. Each incoming request becomes a JSON object containing the method, path, query params, headers, body, IP, and timestamp. New events are prepended with LPUSH, and the list is capped at 100 entries with LTRIM. Both the list and the config key get their TTL refreshed to 24 hours on each incoming request.

Config — a Redis string at hook:{id}:config. This stores what the endpoint should respond with: status code, content type, body, and an optional forward URL. The default is a 200 OK with an empty JSON object.

How the browser gets updates

The page opens a Server-Sent Events connection to the same endpoint via GET. The server reads historical events from Redis immediately and streams them, then polls Redis every second for new ones.

It’s not pub/sub. There’s no Redis channel or subscription. The SSE handler just calls LRANGE hook:{id}:events 0 -1 every second and sends anything with an ID higher than what it already sent. It’s simple, and it works fine at this scale — one user per endpoint, low traffic.

Pub/sub would be cleaner in theory, but Upstash’s HTTP API doesn’t support persistent subscriptions in a way that maps naturally to a long-running SSE stream on serverless. Polling Redis once a second costs a few hundred requests per hour per open tab, which is acceptable.

Why not a database

A relational database would work, but it’s more than needed here. Webhook history is inherently ephemeral — nobody needs a request from three days ago. The data is small (capped at 100 events, each at most 32KB). There’s no querying beyond “give me all events for this ID”. And the 24-hour TTL is a first-class requirement, not an afterthought.

Redis’s list type maps directly to the problem: ordered, capped, fast to append and read. The TTL is one line. There’s no schema to define, no migration to write, no index to maintain.

Related tool

Try webhookinspector directly in your browser.

Open tool →