Quickstart: daemon mode
Daemon mode is for when something else should receive events on your behalf — a webhook endpoint, a Kafka topic, a gRPC service, a browser. The daemon runs the sink engine (ordering, batching, retries, checkpoints) and exposes a control plane: an admin API and a dashboard.
The core ships zero sinks. Run npx walcast serve with no sinks configured and you get this — the error message is the onboarding:
walcast daemon needs at least one sink plugin — the core transports nothing by itself.
Install one and add it to walcast.config.json:
npm install @walcast/sink-webhook HTTP POST delivery (HMAC-signed, durable)
npm install @walcast/sink-sse live Server-Sent Events endpoint (ephemeral)
npm install @walcast/sink-kafka Kafka, exactly-once into the topic (durable)
npm install @walcast/sink-grpc push batches to your gRPC server (durable)
Example config:
{
"sinks": [
{
"use": "@walcast/sink-webhook",
"config": { "url": "https://example.com/hooks/walcast", "secret": "..." }
}
]
}
Writing your own transport is a ~50-line plugin: https://github.com/ManasMadan/walcast/tree/master/templates/plugin
(If you just want events in your own code, you don't need the daemon at all:
import { Walcast } from 'walcast' — your code is the sink.)1. Install a sink
npm install walcast @walcast/sink-webhookSinks are installed in your project; the daemon resolves them from the working directory (a local file like ./my-sink.js works too).
2. Write walcast.config.json
{
"db": "postgres://user:pass@localhost:5432/mydb",
"sinks": [
{
"use": "@walcast/sink-webhook",
"config": {
"url": "https://example.com/hooks/walcast",
"secret": "your-hmac-secret"
}
}
]
}Environment variables override the file: DATABASE_URL / WALCAST_DB, WALCAST_PORT, WALCAST_AUTH_TOKEN, and more — see the config reference.
3. Start it
npx walcast serveThe daemon runs setup() (idempotent), loads each sink, starts the engine, and listens on 127.0.0.1:7717 by default. On startup it logs something like:
walcast daemon started port=7717 sinks=["sink-webhook (durable)"] publication=walcast slot=walcast
generated admin token (set server.authToken or WALCAST_AUTH_TOKEN to pin one) token=xxxxxxxx
dashboard: http://127.0.0.1:7717/ui/?token=xxxxxxxx4. Open the dashboard
The admin API and dashboard are protected by a bearer token. If you didn't set one (server.authToken in the config, or WALCAST_AUTH_TOKEN), a fresh token is generated per start and printed in the log. Open the printed URL:
http://127.0.0.1:7717/ui/?token=<token>The dashboard shows engine throughput, slot lag, and per-sink status with pause/resume. The same data is available from the API:
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:7717/api/statsGET /healthz needs no auth. Full surface: HTTP API reference.
What the engine guarantees
- Events fan out to every sink in commit order.
- Durable sinks get bounded queues; when a queue fills, the engine stops reading from replication — WAL accumulates on the server, never dropped. Failed deliveries retry with exponential backoff and jitter; after
maxAttempts(default 10) the sink is paused with its last error, holding the slot until you resume it from the API/UI. - Ephemeral sinks (like SSE) are best-effort and can never hold WAL back.
- The replication slot advances to the minimum acknowledged LSN across durable sinks only. Per-sink checkpoints are stored in a
walcastschema inside your source database — nothing extra to run.
Next: pick a sink — webhook, SSE, Kafka, gRPC — or write your own.