Postgres changes on NATS JetStream.
One message per change, acknowledged by the stream, with the idempotency key as Nats-Msg-Id so JetStream drops redeliveries for you.
The NATS sink publishes each change to a subject covered by a JetStream stream and waits for the stream's acknowledgement. The idempotency key becomes the message id, so JetStream's dedupe window discards a retried publish server-side, and the ordering key travels as a header.
How delivery works
| Aspect | Behaviour for NATS JetStream |
|---|---|
| Message | Data = the change JSON; header ordering_key; Nats-Msg-Id = idempotency key. |
| Batching | Published one at a time in sequence order, each acknowledged before the next. |
| Ordering | Sequential publishes keep commit order on the subject. |
| Idempotency | Server-side via Nats-Msg-Id within the stream's duplicate window. |
| On failure | A failed request is retried up to 5 times with exponential backoff (30 s, 1, 2, 4 min, with jitter). After the fifth failure the message is parked as undelivered, the console shows it with the last error, and one click replays it — later changes to the same row wait behind it so order is preserved. |
| Connection check | Connects and resolves the stream that covers the subject. |
Set up in three steps
-
Connect your database
Paste a connection string and press Test. Waltail checks the version (12+), that
wal_levelislogical, that the user may replicate, and that a slot is free — and shows the fix for anything that fails. Then it creates its own publication and replication slot. Connection guide → -
Add NATS JetStream as a destination
Pick the tables to stream, choose NATS JetStream, and fill in Server URL, Subject. Press Test — Waltail checks it can reach and write to NATS JetStream before anything is saved.
-
Change a row
Insert or update a row. The console shows the first event as it is captured and delivered; from then on, every committed change follows within about a second. Have rows that already exist? Run a backfill — confirm the estimate and they arrive through NATS JetStream as
readevents.
Configuration
Fields as the console asks for them. Secrets are encrypted at rest and never shown again.
| Field | Console label | Required | Notes |
|---|---|---|---|
url | Server URL | Yes | Credentials in the URL, e.g. nats://user:pass@host:4222. |
subject | Subject | Yes | Must be covered by an existing JetStream stream. |
Every sink also has two tuning settings: batch size (messages per request, default 100) and rate limit (messages per second, default unlimited). Changes to one row are never in flight twice at once.
Example
Every change is this JSON envelope — data is the row after the change, old the row before it when replica identity provides it, null otherwise:
{
"op": "update",
"table": "public.orders",
"data": { "id": 42, "status": "shipped", "total": 19.99 },
"old": { "id": 42, "status": "paid", "total": 19.99 },
"seq": 137,
"idempotency_key": "9f1c3d2e-6b4a-4c7e-9d0f-2a1b3c4d5e6f",
"commit_lsn": "0/1A2B3C8"
}
Questions
Does it work with Synadia Cloud?
Yes — any NATS server with JetStream enabled, reachable with the URL you provide.
Why one message at a time?
JetStream's publish acknowledgement is per message; sequential publishes keep order without buffering on our side.