Developer map of the Verae Time × Zapier NATS message
fabric: what was designed, what is actually running on NS1, how
a Zapier timestamp job reaches api.veraetime.net (or the
mock) and comes back, and what is still unwired.
Forgejo:
https://git.georgelambert.org/marchon/dev-overview
SSH:
ssh://git@git.georgelambert.org:2223/marchon/dev-overview.git
Catalog:
https://zapier.georgelambert.org/packages/dev-overview/
Date: 2026-09-14
Zapier cloud never connects to NATS. It talks HTTPS
to https://zap.zapier.georgelambert.org (access-zapier)
then middleware. api.veraetime.net is HTTPS
only — middleware is the NATS client.
| Doc | What |
|---|---|
| 00-sources.md | Every other doc that names a NATS address or TCP route |
| 01-nats-routes.md | Transport ports + every verae.* address: designed vs
running |
| 02-endpoint-io.md | Per-address: purpose, inbound from, outbound to, how to connect Verae API |
| 03-zapier-verae-path.md | Zapier → middleware → Verae timestamp server → poll → events → Zapier |
| 04-gaps-and-finish.md | Modules not connected; billing/event/aux; ordered finish list |
| 05-developer-credentials.md | URLs, tokens, lab API keys, staff/IAM, git, env cheat sheet |
nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
JetStream streams (replicas=3, file): ZAPIER_JOBS,
ZAPIER_EVENTS, ZAPIER_WEBHOOKS,
ZAPIER_USAGE, VERAE_ARCHIVE.
Public token-gated client edge (not cluster routes):
nats://<token>@70.88.205.138:4222,
wss://nats.zapier.georgelambert.org. Do not put this in
Zapier.
This inventory is compiled from code and from these docs. Do not
invent extra verae.* names.
| Path | What it names |
|---|---|
packages/docs-master/modules-and-nats.md |
Module catalog, subject table, HTTPS vs NATS |
packages/docs-master/MESSAGE-FLOWS.md |
Create/wait/includeAttached/batch Merkle |
packages/docs-master/archive-nats.md |
Archive put/query/reply |
docs/02-architecture/nats-subjects.md |
verae.zapier.* payloads + streams |
docs/02-architecture/modules-and-nats.md |
Same catalog as docs-master |
docs/02-architecture/overview.md |
Poller / events / webhooks |
packages/overview/03-nats-cluster.md |
Cluster URLs, who may connect |
packages/overview/06-address-routing.md |
Address pattern + plane mapping |
packages/verae-ops/06-linking-services.md |
How services attach |
packages/verae-nats-cluster/scripts/ensure-streams.sh |
Live stream create |
packages/verae-nats-cluster/conf/nats.conf.tmpl |
:4222 / :6222 / :7422 /
monitor :8222 |
packages/verae-nats-cluster/conf/public-edge.conf.tmpl |
Public leaf :4222, WS :14422 |
packages/verae-fleet/overlays/ns1/fleet.json |
Enabled vs in-process workers |
packages/verae-keep
units.px-worker.json |
Worm/tree on LXC 510 |
packages/verae-zapier-middleware/src/nats/subjects.js |
Product subjects + consumers |
packages/zappier-account-balance/src/subjects.js |
Billing subjects |
packages/verae-access-authz/src/subjects.js +
policy.js |
Authz + plane allow lists |
packages/zappier/src/billing-nats.ts |
Edge billing client |
NATS.md (Forgejo module copies also live under
docs-master/modules/)| Package | Addresses |
|---|---|
verae-zapier-middleware |
jobs.watch out, jobs.events in,
archive.query via aggregator |
verae-jobs-events |
jobs.events; exclusive →
verae.internal.jobs.events |
verae-request-splitter |
archive.put |
verae-archive-worm |
archive.put / query /
reply.<id> |
verae-tree-node |
same, kind: tree |
verae-archive-aggregator |
query out, reply.<id> in |
zappier-account-balance |
verae.billing.* |
zappier (edge) |
package NATS.md is stale (“does not publish”);
code publishes usage/payment/credit/customer.put when
NATS_URL is set. Use
docs-master/modules/zappier/NATS.md |
zappier-customer-service |
statement.get, balance.adjust |
zappier-sales-pricing |
statement.get |
zappier-accounting-export |
statement.get |
zappier-identity |
identity.bind / lookup |
verae-access-authz |
authz.check / authz.deny |
verae-access-web |
access.web.in / access.web.billing.* |
verae-access-api |
access.api.* |
verae-access-zapier |
access.zapier.* then HTTPS to middleware |
verae-access-leaf |
access.leaf.in |
verae-access-staff |
access.staff.* |
verae-nats-process |
verae.example.process.* (template) |
verae-nats-accounts |
policy only (INTERNAL vs LEAF) |
verae-zapier-simulator |
in-process replay of product addresses |
verae-keep / verae-fleet /
verae-uptime / verae-backup |
no product subjects |
Simulator src/pipeline.js and
src/monitors.js list the same product addresses for traces;
they are not live subscribers.
Designed = named in docs or in code.
Running = a process on NS1 is subscribed or publishing
it today, and (for durable work) a JetStream stream
exists.
Cluster name verae. Internal clients use the three LXC
on vmbr1. Public edge is a leaf, not a
second cluster.
Legend: JS = JetStream stream; RR = core request-reply; pub = core or JS fire-and-forget; Q = queue group.
These are NATS server routes, not
verae.* subjects.
| Endpoint | Designed | Running | Who |
|---|---|---|---|
10.10.10.21–23:4222 client |
yes | yes | middleware, access-*, billing, worm/tree, identity, CS/sales/acct |
10.10.10.21–23:6222 cluster routes |
yes | yes (private vmbr1 only) |
nats-a/b/c to each other |
10.10.10.21–23:7422 leaf hub |
yes (public-edge) | yes | public edge remotes |
127.0.0.1:8222 monitor on each LXC |
yes | yes | operators on the guest |
70.88.205.138:4222 public client |
yes (token) | yes | leaf edge; not Zapier |
127.0.0.1:14422 WS on edge |
yes | yes | Caddy → wss://nats.zapier.georgelambert.org |
127.0.0.1:8229 edge monitor |
yes | yes | host loopback |
:6222 / :7422 / :8222 on
vmbr0 |
no | must stay closed | — |
Host leftover 127.0.0.1:4222 (docker-proxy) +
10.10.10.1:4222 socat |
lab leftover | present | do not point product NATS_URL
here |
MQTT :1883 |
study only | removed | — |
Internal keep/fleet/middleware URL:
nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
Public (token in /etc/nats/public-edge.token on NS1):
nats://<token>@70.88.205.138:4222. Isolation on the
hub is still $G + private LAN. nkeys/mTLS sketched in
verae-nats-accounts, not flipped.
Created by verae-nats-cluster/scripts/ensure-streams.sh
(middleware ensureStreams also creates the first
three).
| Stream | Subjects | Replicas | Storage | Who consumes |
|---|---|---|---|---|
ZAPIER_JOBS |
verae.zapier.jobs.watch |
3 | file | job-poller (in middleware process), Q job-poller |
ZAPIER_EVENTS |
verae.zapier.jobs.events |
3 | file | middleware wait + webhook router
(event-webhook-router); optional exclusive
jobs-events |
ZAPIER_WEBHOOKS |
verae.zapier.webhooks.deliver |
3 | file | webhook-deliver (in middleware), Q webhook-deliver |
ZAPIER_USAGE |
verae.zapier.usage |
3 | file | no durable consumer (USAGE_WRITER
named only) |
VERAE_ARCHIVE |
verae.archive.> |
3 | file | worm + tree on LXC 510 (keep); aggregator not fleet-enabled |
Billing is core request-reply / pub, not a JetStream stream.
| Address | Kind | Designed | Running | Notes |
|---|---|---|---|---|
verae.zapier.jobs.watch |
JS work Q job-poller |
yes | yes | middleware publishes; in-process poller consumes |
verae.zapier.jobs.events |
JS events | yes | yes | poller publishes; wait() subscribes; webhook worker routes |
verae.zapier.webhooks.deliver |
JS work Q webhook-deliver |
yes | yes | same Node process as HTTP |
verae.zapier.usage |
JS optional | yes | stream yes, consumer no | middleware entitlement is local files |
verae.internal.jobs.events |
core republish | yes (jobs-events exclusive) |
partial | only if JOBS_EVENTS_EXCLUSIVE=1 |
verae.billing.statement.get |
RR Q account-balance |
yes | yes | portal, CS, sales, accounting, access-web/api |
verae.billing.balance.get |
RR | yes | yes | account-balance |
verae.billing.balance.adjust |
RR | yes | yes | CS credits, portal reload |
verae.billing.usage.recorded |
pub | yes | yes if edge NATS_URL set |
zappier/src/app.ts onUsage →
natsPublish; account-balance subscribes |
verae.billing.payment.recorded |
pub | yes | yes if NATS_URL |
portal reload (portal.ts) |
verae.billing.credit.applied |
pub | yes | yes if NATS_URL |
admin/CS (admin.ts) |
verae.billing.customer.put |
pub | yes | yes if NATS_URL |
ledger.ts; account-balance applies |
verae.access.authz.check |
RR Q access-authz |
yes | yes | fleet keepFloor |
verae.access.authz.deny |
pub audit | yes | yes (on deny) | no SIEM sink |
verae.access.web.in /
verae.access.web.billing.* |
ingress | yes | yes | mapped after authz,
e.g. verae.access.web.billing.statement.get |
verae.access.api.* |
ingress | yes | yes | |
verae.access.zapier.* |
ingress | yes | yes | HTTPS plane; Zapier never speaks NATS |
verae.access.leaf.in /
verae.access.leaf.* |
ingress | yes | yes | no billing |
verae.access.staff.* |
ingress | yes | yes | CS/sales/accounting |
verae.identity.bind |
RR Q identity |
yes | yes | fleet |
verae.identity.lookup |
RR Q identity |
yes | yes | |
verae.archive.put |
JS (VERAE_ARCHIVE) |
yes | partial | splitter in-process in middleware; worm/tree on 510 consume |
verae.archive.query |
broadcast (no Q) | yes | partial | live aggregator hook TBD in middleware (simulator has it) |
verae.archive.reply.<id> |
replies on bloom hit | yes | partial | worm/tree on 510 can reply; aggregator not fleet-enabled |
verae.splitter.in |
future | yes (docs) | no | splitter stays a library |
verae.example.process.in / .out /
.reply.* |
template | yes | no | verae-nats-process is a model |
bench.* |
lab only | n/a | ephemeral | speed studies; not product |
Plane mapping (verae-access-authz): internal
verae.billing.statement.get + plane web →
ingress verae.access.web.billing.statement.get. Planes
never publish internal subjects until authz.check returns
allow.
| Name | Stream / subject | Running |
|---|---|---|
job-poller |
ZAPIER_JOBS / jobs.watch |
yes (in-process) |
event-webhook-router |
ZAPIER_EVENTS / jobs.events |
yes (in-process) |
webhook-deliver |
ZAPIER_WEBHOOKS / webhooks.deliver |
yes (in-process) |
usage-writer |
ZAPIER_USAGE / verae.zapier.usage |
no |
jobs-events-exclusive |
ZAPIER_EVENTS |
no unless JOBS_EVENTS_EXCLUSIVE=1 |
packages/verae-fleet/overlays/ns1/fleet.json
(2026-09-14):
| Service | fleet enabled |
Actual lab |
|---|---|---|
| middleware-http | true | running; starts job-poller + webhook workers
in-process when NATS_ENABLED |
| job-poller | false | covered by middleware; do not spawn a second copy |
| webhook-deliver | false | same |
| archive-aggregator | false | not running as a fleet worker |
| archive-worm / tree-node | false on host fleet | running on LXC 510 via keep
units.px-worker.json (1 worm + 1 tree) |
| account-balance, access-*, identity, jobs-events, CS/sales/acct, staff-iam/session, zappier-edge | true keepFloor | keepFloor |
| zapier-simulator | true, keepFloor false | optional |
| nats | managed false | 3-node cluster + leftover host loopback |
Each product NATS address: what it is for, who sends, who receives, how to attach.
Connect with a long-lived client (reconnect-per-message is the anti-pattern):
NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
Public edge (token in /etc/nats/public-edge.token on
NS1): nats://<token>@70.88.205.138:4222. Do not put
that URL in Zapier or in Verae.
verae.zapier.jobs.watch| For | Work queue: “poll Verae until this jobId is
terminal.” |
| In from | middleware enqueueWatch after
POST /api/timestamp (or mock) returns
jobId |
| Out to | job-poller worker (queue job-poller, durable on
ZAPIER_JOBS) |
| Then | poller calls Verae GET /api/status/{jobId} over
HTTPS, not NATS |
Payload: tenantId, veraeUserId,
jobId, tokenRef, enqueuedAt,
attempt, maxAttempts, intervalMs,
traceId. JWT stays in middleware (tokenRef);
never on Zapier.
Connect Verae-side: do not
subscribe this from api.veraetime.net. The poller is the
Verae HTTP client. To add another watcher, start another
job-poller consumer in the same queue group.
verae.zapier.jobs.events| For | Terminal outcomes: timestamp.completed /
failed / timeout |
| In from | job-poller after Verae status is not pending |
| Out to | (1) middleware waitForJobEvent (wait HTTP); (2) webhook
router → webhooks.deliver; (3) optional
verae-jobs-events mailbox :3030 |
If JOBS_EVENTS_EXCLUSIVE=1, only
verae-jobs-events is the durable consumer and republishes
verae.internal.jobs.events. Default lab: middleware wait +
webhook worker share the stream.
Payload: event, tenantId,
jobId, status, traceId,
emittedAt.
verae.zapier.webhooks.deliver| For | HTTPS POST to hooks.zapier.com (REST Hook) |
| In from | webhook worker on completed/failed events |
| Out to | Zapier cloud HTTPS, never NATS |
Payload: hookId, tenantId,
targetUrl, event, payload,
attempt, traceId. Slow consumer: keep replica
floors on the worker if you split it out of middleware.
verae.zapier.usage| For | Optional meter of Zap actions on NATS (JetStream) |
| In from | designed: middleware / edge |
| Out to | designed: USAGE_WRITER → books |
| Running | stream exists; no writer; middleware uses local usage files |
Prepaid meter that is wired uses
verae.billing.usage.recorded (core pub), not this stream.
Finish: either publish here from recordUsage or drop the
stream.
verae.billing.*) — account-balance source of truthRequest-reply unless noted. Queue account-balance.
Not JetStream.
| Address | For | In from | Out to |
|---|---|---|---|
statement.get |
prepaid, credits, usage, payments | portal, CS, sales, accounting, access-web/api | reply to requester |
balance.get |
cents only | internal | reply |
balance.adjust |
CS credit or portal reload | CS, access-web (reload kind only) | books + reply |
usage.recorded |
meter pub | zappier-edge meter() onUsage when
NATS_URL set |
account-balance subscriber (debits prepaid) |
payment.recorded |
reload/invoice pub | portal | books |
credit.applied |
goodwill pub | CS/admin | books |
customer.put |
upsert customer | edge ledger | books |
Authz must pass
verae.access.authz.check before these from a public plane.
Leaf plane cannot call billing (policy).
Middleware recordUsage still writes local tenant
files and does not publish
usage.recorded. Two meters until that is unified.
| Address | For | In | Out |
|---|---|---|---|
verae.access.authz.check |
allow/deny { plane, subject, principal } |
every plane + edge billing-nats.ts |
{ allow, reason }; deny also pubs
authz.deny |
verae.access.authz.deny |
audit | authz on deny | nowhere yet (no SIEM) |
verae.access.web.in /
verae.access.web.billing.* |
browser portal | access-web HTTP | statement.get / balance.adjust kind=reload
after authz |
verae.access.api.* |
customer x-api-key |
access-api | statement; HTTPS to middleware for jobs |
verae.access.zapier.* |
Zapier HTTPS only | access-zapier | jobs.watch via HTTP to middleware, not Zapier NATS; usage.recorded allowed by policy |
verae.access.leaf.in |
S2S { token, target, payload } |
remote leaf | archive + jobs after authz; never billing |
verae.access.staff.* |
CS/sales/accounting | access-staff | billing after authz |
Ingress mapping:
accessAddress(plane, 'verae.billing.statement.get') →
verae.access.<plane>.billing.statement.get.
| Address | For | In | Out |
|---|---|---|---|
verae.identity.bind |
bind email/customerId → veraeUserId |
portal / middleware | reply |
verae.identity.lookup |
lookup by customerId or veraeUserId | same | reply |
Queue identity. Fleet keepFloor.
| Address | For | In | Out |
|---|---|---|---|
verae.archive.put |
WORM/tree write (kind: publicMeta / privateMeta / file
/ tree) |
splitter (in-process from middleware today) | every worm/tree (JS) |
verae.archive.query |
broadcast lookup | aggregator | all worm/tree, no queue group |
verae.archive.reply.<correlationId> |
bloom hit only | worm/tree | aggregator |
Silence = miss. Chain seal does not wait on archive. Bytes never go to the chain.
| Address | For | In | Out | Running |
|---|---|---|---|---|
verae.splitter.in |
future split-out splitter process | middleware | splitter | no |
verae.example.process.in |
template worker | any | example-process Q |
no |
verae.example.process.out |
template fan-out | template | peers | no |
verae.example.process.reply.<id> |
template reply | template | requester | no |
verae.internal.jobs.events |
exclusive mailbox republish | jobs-events | wait/webhook | no unless env |
api.veraetime.net is HTTPS. It does not
subscribe. The glue is middleware:
VERAE_API_BASE_URL=https://api.veraetime.net and
MOCK_VERAE=false.tokenRef.POST /api/timestamp → 202 { jobId }.verae.zapier.jobs.watch.GET /api/status/{jobId} until
completed / failed.verae.zapier.jobs.events.Do not point Verae at NATS_URL. Do
not put NATS in Zapier env. A new worker that must see
seals uses jobs.events or HTTP
GET /zapier/v1/status/{jobId}.
Template for a new addressed process: clone
verae-nats-process, rename
verae.example.process.*, add the subject to
verae-access-authz policy for the plane that may call
it.
How a timestamp job is submitted, watched, and returned. Lab today:
MOCK_VERAE=true. Live path is the same HTTP contract with
MOCK_VERAE=false.
Zapier cloud --HTTPS only--> zap.zapier.georgelambert.org (access-zapier)
|
v HTTPS
middleware :3100 /zapier/v1
|
+---------------+----------------+
| HTTPS | NATS (private cluster)
v v
api.veraetime.net jobs.watch → poller
POST /api/timestamp GET /api/status/{jobId}
GET /api/status/{jobId} |
POST /api/verify v
jobs.events
|
+--------------------+--------------------+
| wait subscriber | webhook-deliver
v v
HTTP response to Zap POST hooks.zapier.com
Zapier never has NATS_URL. Verae never has
NATS_URL.
POST https://zap.zapier.georgelambert.org/zapier/v1/timestamp
with portal API key zmw_… (not a Verae JWT).meter() records
cents; if NATS_URL is set,
natsPublish(verae.billing.usage.recorded).POST /zapier/v1/timestamp.checkEntitlement (local plan limits
today).veraeClient.createTimestamp:
jobId.POST https://api.veraetime.net/api/timestamp with JWT →
202 { jobId }.verae.archive.put (bytes never go to chain).enqueueWatch → JetStream
verae.zapier.jobs.watch.{ jobId, sha256, existing }
back to Zapier immediately.Same SHA-256 is idempotent (existing: true).
The poller is in the middleware Node process when
NATS_ENABLED=true (startJobPollerWorker).
Fleet does not spawn a second copy
(job-poller.enabled: false).
job-poller on ZAPIER_JOBS
/ verae.zapier.jobs.watch.tokenRef → tenant Verae password →
POST login → JWT (stays in middleware).GET /api/status/{jobId} (mock or live timestamp
server).pending → nak after
intervalMs (retry).completed → publish jobs.events
{ event: timestamp.completed, status, jobId, tenantId, traceId }.failed → timestamp.failed. Too many
attempts → timestamp.timeout.ack the watch message.Live OpenAPI: poll until completed /
failed. Hash lookup is 501 on live
(lookupHash). Map certificate,
timestamp, blockIndex, sha256
into the receipt Zap samples expect.
Create Timestamp and Wait →
POST /zapier/v1/timestamp/wait.
waitForJobEvent(jobId, WAIT_TIMEOUT_MS) subscribes to
verae.zapier.jobs.events.jobId event: return status JSON
(certificate / receipt).{ status: pending, jobId } so the REST Hook
can finish the Zap.includeAttached: after seal, aggregator should
verae.archive.query; bloom hits merge extra receipts.
Simulator does this; live aggregator hook is TBD.
POST /zapier/v1/hooks. On jobs.events,
middleware enqueues
verae.zapier.webhooks.deliver. Worker
POSTs targetUrl (hooks.zapier.com).GET /zapier/v1/status/{jobId} →
middleware GET Verae (or last event).POST /zapier/v1/verify → middleware → Verae
POST /api/verify. No job watch.
From MESSAGE-FLOWS.md:
POST /v1/timestamp/batch — one item per line.POST /api/timestamp on the root).verae.archive.put
kind: tree.includeTree=true.verae.archive.query
kinds=["tree"].archive.reply.<id> with
proof.Live wait path does not yet call the aggregator, so tree-leaf receipts are not merged on NS1.
api.veraetime.netStill blocked on operator bind credentials. When ready:
# middleware env
MOCK_VERAE=false
VERAE_API_BASE_URL=https://api.veraetime.net
NATS_ENABLED=true
NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
JETSTREAM_REPLICAS=3Then: working Verae login, one live seal, confirm 202/status/verify,
map receipt fields, REST Hook to a private Zap. See
zapier-decisions/STATUS.md “Once MOCK_VERAE=false”.
| Hop | Designed | Running |
|---|---|---|
| Edge meter | verae.billing.usage.recorded |
coded; fires when edge has
NATS_URL |
| Account-balance | subscribe + debit prepaid; 402 if empty | subscriber running; 402 from books not proven on every Zap |
| Middleware quota | local files via recordUsage |
running; not NATS |
verae.zapier.usage |
optional JS meter | stream only |
export NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
# clone verae-nats-process, rename subjects, register in verae-fleetJobs stay: HTTP in from Zapier → HTTPS to Verae → NATS watch/events → HTTP back to Zapier.
Modules and addresses that are designed but not fully connected, not running as fleet workers, or not on the live Verae/Zapier path.
| Item | State | To finish |
|---|---|---|
Zapier Platform push |
no login | zapier login → register →
MIDDLEWARE_BASE_URL=https://zap.zapier.georgelambert.org →
push private app |
| Live Verae | MOCK_VERAE=true |
credentials; MOCK_VERAE=false; prove one seal |
| nkeys / mTLS | cluster $G only |
verae-nats-accounts sketch; flip only after every
NATS_URL has creds |
| Hash lookup on live OpenAPI | 501 | Verae API gap or our store |
| Module | Running? | NATS | Gap |
|---|---|---|---|
| middleware HTTP + in-process job-poller + webhook-deliver | yes | watch/events/deliver | live Verae + Zapier push |
| account-balance | yes | billing RR + usage.recorded sub |
prove 402 from books on a Zap when prepaid is 0 |
| zappier-edge | yes | usage.recorded / payment.recorded /
credit.applied / customer.put if
NATS_URL |
confirm live process env; Stripe live |
| access-* + authz | yes | authz + ingress | — |
| identity | yes (fleet) | bind/lookup | confirm portal always binds veraeUserId |
jobs-events mailbox :3030 |
yes (fleet) | events counter | exclusive consumer off unless
JOBS_EVENTS_EXCLUSIVE=1 |
| CS / sales / accounting | yes | statement/adjust | staff UX |
| archive-worm + tree-node | yes on LXC 510 (keep) | archive.put/query/reply |
only 1+1 copies; fleet overlay
enabled: false for extra replicas |
| archive-aggregator | no as fleet worker | query/reply | enable overlay or finish in-process hook
(includeAttached TBD in middleware) |
| request-splitter | library in middleware | archive.put |
verae.splitter.in not used |
ZAPIER_USAGE / USAGE_WRITER |
stream yes | verae.zapier.usage |
no consumer; middleware usage is local files |
| fleet job-poller / webhook-deliver extra copies | overlay false | same subjects | only enable if you want SSH-spread replicas instead of in-process (same queue names) |
| Module | Address | What to do |
|---|---|---|
verae-nats-process |
verae.example.process.* |
template only; clone to add a function |
verae-activate |
none (HTTPS Zapier) | optional tiny app; needs Zapier login |
verae-zapier (full app) |
HTTPS | push after login |
| simulator | in-process fake NATS | local :3847; not production |
verae-nats-accounts |
nkeys policy | not flipped |
| Module | NATS | Notes |
|---|---|---|
verae-keep |
none | HTTP health; restarts units |
verae-fleet |
none | catalog + keepFloor |
verae-uptime |
none | off-box HTTPS GET of public doors |
verae-backup |
none | disk / JetStream snapshot; no live event subject |
verae-bootstrap / verae-deploy /
verae-proxmox / verae-ops |
none | install/link |
| staff-iam / staff-session / staff-ui | none | HTTP cookies |
| public NATS edge | leaf, token | do not use from Zapier; rotate token; later nkeys LEAF vs INTERNAL |
| Signal | Designed | Running | Finish |
|---|---|---|---|
| Timestamp/verify meter (edge) | verae.billing.usage.recorded |
code path exists (zappier/src/app.ts
onUsage) |
Confirm NATS_URL on live edge; 402 from account-balance
when prepaid empty |
| Middleware quota | local files recordUsage |
yes | Either publish usage.recorded too or drop duplicate
quota |
| Optional JS meter | verae.zapier.usage + USAGE_WRITER |
stream only | Wire or delete |
| Portal reload | payment.recorded + balance.adjust |
coded | confirm both fire on Stripe/test reload |
| CS credit | balance.adjust + credit.applied |
coded | confirm applied pub on live |
| Customer upsert | customer.put |
coded | — |
| Authz deny audit | verae.access.authz.deny |
running on deny | no SIEM sink |
| Job lifecycle log | jobs.events |
running | mailbox counts only — add structured log/export if needed |
| Archive put/query | VERAE_ARCHIVE |
510 worm/tree | aggregator not in wait path |
| Public edge | leaf | running | rotate token; do not expose :6222 |
job-poller.NATS_URL so
usage.recorded actually lands; then 402 from books. Unify
or drop middleware local recordUsage.includeAttached and tree proofs work on
wait/status.push +
MOCK_VERAE=false.jobs.watch → live
GET /api/status → jobs.events → wait body +
REST Hook.jobs-events mailbox; SIEM on
authz.deny and jobs.events;
verae.splitter.in if splitter is split out;
USAGE_WRITER or delete ZAPIER_USAGE.HTTPS zap/mw → mock create → jobs.watch → in-process
poller → mock status completed → jobs.events → wait returns
mock certificate. Archive extras and live chain are the missing
middle.
Lab pack for the Verae Time × Zapier stack on
NS1.GEORGELAMBERT.ORG (70.88.205.138).
Values below were checked 2026-09-14. These are
lab defaults, not production secrets. Rotate the public
NATS token and admin secrets before treating this as a customer
environment.
Zapier cloud never gets a NATS URL or a Verae password.
| Host | Role |
|---|---|
70.88.205.138 |
NS1 Proxmox. Public HTTPS + public NATS client edge |
10.10.10.20 |
LXC 510 verae-px-worker (worm + tree) |
10.10.10.21–23 |
LXC 511–513 nats-a/b/c JetStream cluster
verae |
SSH to the box: ssh marchon@70.88.205.138
Git SSH:
ssh://git@git.georgelambert.org:2223/marchon/<repo>.git
(port 2223)
Wildcard *.zapier.georgelambert.org →
70.88.205.138.
| URL | Process | Port | Last check |
|---|---|---|---|
| https://zapier.georgelambert.org/ | docs catalog | files | 200 |
| https://git.georgelambert.org/ | Forgejo | :443 / SSH :2223 | 200 |
| https://portal.zapier.georgelambert.org/ | access-web → /portal/ |
:3021 | 302 → portal |
| https://api.zapier.georgelambert.org/health | access-api | :3022 | 200 |
| https://leaf.zapier.georgelambert.org/health | access-leaf | :3023 | 200 |
| https://zap.zapier.georgelambert.org/health | access-zapier | :3024 | 200 |
| https://staff.zapier.georgelambert.org/ | access-staff | :3025 | 302 login |
| https://login.zapier.georgelambert.org/ | staff-session | :3027 | 200 |
| https://iam.zapier.georgelambert.org/health | staff-iam | :3028 | 200 (users: 5) |
| https://cs.zapier.georgelambert.org/ | customer-service | :3011 | 302 |
| https://sales.zapier.georgelambert.org/ | sales-pricing | :3012 | 302 |
| https://acct.zapier.georgelambert.org/ | accounting-export | :3013 | 302 |
| https://mw.zapier.georgelambert.org/health | middleware | :3100 | 200 mockVerae:true natsConnected:true |
| https://fleet.zapier.georgelambert.org/ | fleet console | :3850 | 302 |
| https://nats.zapier.georgelambert.org/ | NATS WebSocket (Caddy) | 14422 | 400 on GET (expected; it is WS) |
Loopback on NS1 (not public): zappier-edge
127.0.0.1:13000 (admin + Stripe + SQLite).
Tunnel:
ssh -L 13000:127.0.0.1:13000 marchon@70.88.205.138
# then http://127.0.0.1:13000/admin and /docsOperator console tunnel:
ssh -L 3850:127.0.0.1:3850 marchon@70.88.205.138
No token. Private vmbr1 only.
NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
JETSTREAM_REPLICAS=3
| Port | Bind | Use |
|---|---|---|
:4222 |
10.10.10.21–23 |
clients |
:6222 |
same, vmbr1 |
cluster routes — do not publish |
:7422 |
same | leaf hubs for the public edge — do not publish |
:8222 |
127.0.0.1 on each LXC |
monitor |
From a laptop you cannot reach
10.10.10.x unless you are on NS1 or you tunnel:
ssh -L 14222:10.10.10.21:4222 -L 14223:10.10.10.22:4222 -L 14224:10.10.10.23:4222 marchon@70.88.205.138
export NATS_URL=nats://127.0.0.1:14222,nats://127.0.0.1:14223,nats://127.0.0.1:14224Live token (file /etc/nats/public-edge.token on
NS1):
4743a49383ce2ce86e99d9d41c45c0004fe75eb23ef3a2b9
nats://4743a49383ce2ce86e99d9d41c45c0004fe75eb23ef3a2b9@70.88.205.138:4222
wss://nats.zapier.georgelambert.org
CONNECT with that token returns PONG
(checked 2026-09-14). auth_required: true. Server name
nats-public-edge. Do not put this URL in
Zapier. Recreate:
bash packages/verae-nats-cluster/scripts/expose-public.sh
on NS1.
Leftover lab listeners (ignore for new clients): host
127.0.0.1:4222 docker-proxy, 10.10.10.1:4222
socat. zappier-edge process still has
NATS_URL=nats://127.0.0.1:4222 — billing pubs may miss the
3-node cluster until that env is flipped.
nkeys/mTLS: not on. Isolation is LAN + token.
Live: MOCK_VERAE=true, NATS_ENABLED=true,
cluster URL as above, ADMIN_SECRET=change-me-admin
(default), TOKEN_SECRET=dev-secret-change-me (default).
| Field | Value |
|---|---|
| tenantId | tenant-dev-lab-local-89ab82de |
| plan | free |
| API key | zmw_pXdwlUUyMHkAvqj3RRoQ2gOG6yAUQlun |
| mock Verae user | lab-dev / lab-dev-pass |
| veraeUserId | vu_20ed6029b99efb2e |
Proven: GET /zapier/v1/auth/me → valid;
POST /zapier/v1/timestamp → 202 jobId; wait →
mock certificate blockIndex: 42.
KEY=zmw_pXdwlUUyMHkAvqj3RRoQ2gOG6yAUQlun
curl -sS -H "x-api-key: $KEY" https://mw.zapier.georgelambert.org/zapier/v1/auth/me
curl -sS -H "x-api-key: $KEY" -H "content-type: application/json" \
-d '{"sha256":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' \
https://mw.zapier.georgelambert.org/zapier/v1/timestamp
curl -sS -H "x-api-key: $KEY" -H "content-type: application/json" \
-d '{"sha256":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}' \
https://mw.zapier.georgelambert.org/zapier/v1/timestamp/waitCustomer API plane (forwards x-api-key to
middleware):
curl -sS -H "x-api-key: $KEY" -H "content-type: application/json" \
-d '{"sha256":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' \
https://api.zapier.georgelambert.org/v1/timestamphttps://zap.zapier.georgelambert.org is the Zapier
plane. It does not currently forward
x-api-key on timestamp (401 if you curl it with a key).
Zapier Platform push is still waiting on login; when it is live, set
MIDDLEWARE_BASE_URL=https://zap.zapier.georgelambert.org
and use a zmw_… key in the Zap.
Mint another key (mock Verae accepts any username/password):
curl -sS -H "content-type: application/json" \
-d '{"email":"you@lab.local","name":"You","veraeUsername":"you","veraePassword":"you-pass"}' \
https://mw.zapier.georgelambert.org/zapier/v1/signupAdmin list/provision:
curl -sS -H "x-admin-secret: change-me-admin" \
https://mw.zapier.georgelambert.org/zapier/v1/admin/tenantsSQLite
/SSD3/moved/home-marchon/verae-stack/packages/zappier/zappier.db.
Bind 127.0.0.1:13000.
Seed customer API keys (x-api-key on
edge /v1/*, not middleware
zmw_):
| Customer | tier | key |
|---|---|---|
| Ada (free) | free | key-ada |
| Grace (pro) | pro | key-grace |
| Linus (business) | business | key-linus |
| Docs Capture | free | key-021851970d8a22c6 |
These keys are for the edge meter / portal.
Middleware jobs need a zmw_… key (section 4).
Seed admin console (/admin,
x-admin-key or login):
| username | password |
|---|---|
admin |
admin-dev-key |
demo |
$$$Adm1n### |
ZAPPIER_ADMIN_KEY default on CS/sales/staff processes:
admin-dev-key.
Stripe: not live (lab quotes only). Env
STRIPE_* unset on the sampled edge process.
Signup more customers at https://portal.zapier.georgelambert.org/portal/
https://login.zapier.georgelambert.org/ · https://iam.zapier.georgelambert.org/
Seed users (defaults; live IAM has these five):
| username | password | role |
|---|---|---|
admin |
admin-dev-key |
owner |
cs |
cs-dev-key |
cs |
sales |
sales-dev-key |
sales |
accounting |
acct-dev-key |
accounting |
operator |
fleet-dev-key |
operator |
STAFF_KEY / session fallback:
admin-dev-key.
https://leaf.zapier.georgelambert.org/health →
verae.access.leaf.in
Live: LEAF_TOKEN=leaf-dev-token
curl -sS -H "content-type: application/json" \
-d '{"token":"leaf-dev-token","target":"verae.archive.put","payload":{},"principal":"dev"}' \
https://leaf.zapier.georgelambert.org/Billing targets are denied on this plane.
| Web | https://git.georgelambert.org/marchon/ |
| SSH | ssh://git@git.georgelambert.org:2223/marchon/<name>.git |
| User | marchon |
| Overview of routes | https://git.georgelambert.org/marchon/dev-overview |
| Monorepo | master-zapier-plan-draft |
| Bootstrap | https://git.georgelambert.org/marchon/verae-bootstrap |
GIT_SSH_COMMAND="ssh -p 2223" git clone ssh://git@git.georgelambert.org:2223/marchon/dev-overview.gitAdd your SSH public key on Forgejo (user marchon or a new account). Do not commit deploy tokens.
Clone-all:
git clone ssh://git@git.georgelambert.org:2223/marchon/verae-bootstrap.git
cd verae-bootstrap
export VERAE_SRC=$HOME/verae-src
bash scripts/fetch.sh ns1-all-in-oneexport NATS_URL=nats://10.10.10.21:4222,nats://10.10.10.22:4222,nats://10.10.10.23:4222
export JETSTREAM_REPLICAS=3
export NATS_ENABLED=true
export MOCK_VERAE=true # flip to false only with live Verae login
export VERAE_API_BASE_URL=https://api.veraetime.net
export AUTHZ_URL=http://127.0.0.1:3020
export ZAPPIER_UPSTREAM=http://127.0.0.1:3100
export ZAPPIER_ADMIN_URL=http://127.0.0.1:13000
export ZAPPIER_ADMIN_KEY=admin-dev-key
export LEAF_TOKEN=leaf-dev-token
export STAFF_IAM_URL=http://127.0.0.1:3028Template worker: clone verae-nats-process, rename
verae.example.process.*.
| Item | Why |
|---|---|
Zapier Platform deploy key / zapier login |
operator has not logged the CLI in |
Live api.veraetime.net username/password |
MOCK_VERAE=true; JWT would stay in middleware |
| NATS nkeys / account creds | cluster is $G + LAN |
| Stripe live secret | lab only |
| lan-134 SSH worker | disabled |
When Zapier login exists:
MIDDLEWARE_BASE_URL=https://zap.zapier.georgelambert.org,
auth field api_key = zmw_….
When live Verae exists: middleware MOCK_VERAE=false +
VERAE_API_BASE_URL=https://api.veraetime.net + working
login. Zapier still never sees that password.