vqueue: Scheduled HTTP Webhooks on Your Own Valkey
We just shipped vqueue, a free HTTP webhook queue layered on top of every Layerbase Cloud Valkey 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.
The Layerbase CLI (formerly SpinDB) (what is the Layerbase CLI?) 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:
- A cache (Valkey or Redis)
- A background job worker (BullMQ on top of Redis, or a managed queue)
- 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 implements the HTTP API surface the open-source @upstash/qstash client expects, backed by the Valkey instance you already provisioned. Endpoints:
POST /v2/batchfor an array of message publishes (this is what@upstash/qstashbatchJSON()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.
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 the Layerbase CLI 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:
npm i -g layerbase
lbase create my-queue --engine valkey --start --connectConnection string:
lbase url my-queue
# redis://default:<password>@127.0.0.1:6379/0Build 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
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
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 bybatchJSON())POST /v2/publish/<destination>for single-message parity- Outbound HS256-JWT signature compatible with
Receiver.verify() Upstash-Forward-*header passthrough- Backed by the Valkey 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 Layerbase CLI commands
# Spin up a fresh Valkey for development
lbase create dev-queue --engine valkey --start --connect
# Get the connection string
lbase url dev-queue
# Stop it when you're done; the data is preserved
lbase stop dev-queue
# Start it again later
lbase start dev-queueThe Layerbase CLI 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 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 instance, no separate signup, no second bill. If you're already running @upstash/qstash against another provider, swap the env vars and ship.
Keep reading
- Your Coding Agent Can Bring Its Own DatabasesI gave Claude Code one API key. It created four databases in 38 seconds, used them, and let them delete themselves. The same loop works on the free plan, in CI, or in your next proof of concept.
- Layerbase vs Tinybird: real-time analytics without the usage meterTinybird turns ClickHouse into a managed real-time analytics API with usage-based billing. Layerbase gives you the same ClickHouse engine on a flat monthly price and leaves the API layer to you. Here is the tradeoff, and when each one wins.
- Run Valkey on Windows (No Docker, No WSL)No official Valkey Windows binary exists anywhere. We manually compiled one so you can run Valkey natively on Windows without Docker or WSL.
- Postgres 19 Beta is live on LayerbaseYou can now create a PostgreSQL 19 Beta 1 database on Layerbase Cloud, or run the beta locally with Layerbase Desktop and SpinDB. Test parallel autovacuum, REPACK, and ON CONFLICT DO SELECT against your real schema months before GA.