vqueue: Scheduled HTTP Webhooks on Your Own Valkey

ValkeyQueuesLayerbase

We just shipped vqueue, a free HTTP webhook queue layered on top of every Layerbase Cloud Valkey or Redis instance. If your app already uses @upstash/qstash to schedule webhooks, you can point it at your own Layerbase database and stop paying a second vendor for the same queue.

What is SpinDB? is the local CLI Layerbase ships for spinning up Valkey on your laptop in 30 seconds. vqueue is the cloud-side product surface, not the CLI, but the same Valkey runs in both places.

The problem

Real apps need more than a database. Almost every web app or serverless project that hits production also wants:

  1. A cache (Valkey or Redis)
  2. A background job worker (BullMQ on top of Redis, or a managed queue)
  3. Scheduled HTTP delivery so a Vercel or Cloudflare function can say "fire this webhook in 30 seconds" without holding a long-running connection

Today most teams pay one vendor for Valkey, a second for managed queues. The schemas, billing, and dashboards never line up. The cache and the queue are on different infrastructure even though they both want Redis-protocol storage underneath.

What vqueue is

vqueue is a small Hono route on Layerbase Cloud that emulates the QStash HTTP API surface, backed by the Valkey or Redis instance you already provisioned. Endpoints:

  • POST /v2/batch for an array of message publishes (this is what @upstash/qstash batchJSON() calls under the hood)
  • POST /v2/publish/<destination-url> for a single message

Each delivery carries an Upstash-Signature JWT signed with HS256, identical to the format that the @upstash/qstash Receiver.verify() call accepts. Drop the env vars from your Layerbase Valkey detail page into your app and the existing client code routes through your database with zero changes.

bash
VQUEUE_URL=https://your-db-name-queue.cloud.layerbase.dev
VQUEUE_TOKEN=<your database password>
VQUEUE_CURRENT_SIGNING_KEY=<derived; shown in the dashboard>

Try it locally with SpinDB first

You can build the receiver side and signature verification on your laptop before touching the cloud at all. Spin up a local Valkey instance:

bash
npm i -g spindb
spindb create my-queue --engine valkey --start --connect

Connection string:

bash
spindb url my-queue
# redis://default:<password>@127.0.0.1:6379/0

Build the receiver locally using the same @upstash/qstash client library. When you're ready to go live, provision a Valkey on Layerbase Cloud and swap the env vars over.

Publisher example

ts
import { Client } from '@upstash/qstash'

const queue = new Client({
  baseUrl: process.env.VQUEUE_URL,
  token: process.env.VQUEUE_TOKEN,
})

await queue.batchJSON([
  {
    url: 'https://your-app.example.com/api/jobs/send-email',
    body: { to: 'you@example.com' },
  },
])

Receiver example

ts
import { Receiver } from '@upstash/qstash'

const receiver = new Receiver({
  currentSigningKey: process.env.VQUEUE_CURRENT_SIGNING_KEY!,
  nextSigningKey: process.env.VQUEUE_NEXT_SIGNING_KEY ?? '',
})

export async function POST(req: Request) {
  const rawBody = await req.text()
  const signature = req.headers.get('upstash-signature') ?? ''
  const isValid = await receiver.verify({
    signature,
    body: rawBody,
    url: 'https://your-app.example.com/api/jobs/send-email',
  })
  if (!isValid) return new Response('bad signature', { status: 403 })
  // ...do the work
}

That's it. Same code shape you'd write against any @upstash/qstash-compatible endpoint; just a different URL and a different signing key.

What's in v1

  • POST /v2/batch (the canonical publish path used by batchJSON())
  • POST /v2/publish/<destination> for single-message parity
  • Outbound HS256-JWT signature compatible with Receiver.verify()
  • Upstash-Forward-* header passthrough
  • Backed by the Valkey or Redis instance you already pay for; free regardless of how many messages you publish

What's coming

  • Scheduled delivery (Upstash-Delay)
  • Dead-letter queue inspection
  • Workflow compatibility layer on top of vqueue
  • Dashboard view of in-flight and recently-delivered messages

For now jobs fire immediately and best-effort with logged failures. That covers the most common use shape (fire-and-forget webhook delivery, async background work, event fan-out), which is roughly 80% of why teams reach for a managed queue.

Useful SpinDB commands

bash
# Spin up a fresh Valkey for development
spindb create dev-queue --engine valkey --start --connect

# Get the connection string
spindb url dev-queue

# Stop it when you're done; the data is preserved
spindb stop dev-queue

# Start it again later
spindb start dev-queue

SpinDB supports 21 database engines with the same install-and-run workflow, so the same project that uses vqueue against a Valkey can also pull a local Postgres, ClickHouse, or libSQL the same way.

Where to find the env vars

Every Layerbase Cloud Valkey or Redis database now shows a vqueue block on its detail page with VQUEUE_URL, VQUEUE_TOKEN, and VQUEUE_CURRENT_SIGNING_KEY ready to copy. The block is hidden on non-eligible engines so you only see it where it makes sense.

Free with any Valkey or Redis instance, no separate signup, no second bill. If you're already running @upstash/qstash against another provider, swap the env vars and ship.

Something not working?