Reviewed-on: #69 Co-authored-by: userAdityaa <aditya.chaudhary1558@gmail.com> Co-committed-by: userAdityaa <aditya.chaudhary1558@gmail.com>
Backend
Rust backend for Caravel. It manages tenants, relays, invoices, and background workers for relay provisioning and billing.
Tech Stack
- Rust (Edition 2024)
- Axum (HTTP API)
- SQLx + SQLite
- Tokio (async runtime + workers)
- Nostr SDK (NIP-98 auth, NIP-17 DMs, NIP-47 wallet connect)
Layout
backend/
migrations/
0001_init.sql
src/
api.rs # Axum routes + NIP-98 auth checks
billing.rs # Invoice generation + collection worker
infra.rs # Zooid sync worker
main.rs # App bootstrap
models.rs # DB models
repo.rs # Data access layer
robot.rs # Nostr robot identity + DM sending
Configuration
Environment variables:
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
SQLite URL. Relative paths are resolved under backend/. |
sqlite://<backend>/data/caravel.db |
HOST |
API bind host (also used for NIP-98 u host check) |
127.0.0.1 |
PORT |
API bind port | 2892 |
ADMINS |
Comma-separated admin pubkeys (hex) | optional |
ALLOW_ORIGINS |
Comma-separated CORS origins. If empty, CORS is permissive. | optional |
ZOOID_API_URL |
Zooid API base URL used by infra worker | required for infra sync |
ZOOID_API_SECRET |
Nostr secret key used for authentication of requests to the zooid API | required |
RELAY_DOMAIN |
Base domain appended to relay subdomains | empty |
LIVEKIT_URL |
LiveKit URL sent to zooid when relay livekit is enabled | optional |
LIVEKIT_API_KEY |
LiveKit API key sent to zooid | optional |
LIVEKIT_API_SECRET |
LiveKit API secret sent to zooid | optional |
BLOSSOM_S3_ENDPOINT |
S3-compatible endpoint URL for Blossom; omit for AWS S3 | optional |
BLOSSOM_S3_REGION |
S3 region; with bucket, access key, and secret enables S3 for Blossom | optional |
BLOSSOM_S3_BUCKET |
S3 bucket name | optional |
BLOSSOM_S3_ACCESS_KEY |
S3 access key ID | optional |
BLOSSOM_S3_SECRET_KEY |
S3 secret access key | optional |
NWC_URL |
Platform NWC URL used to generate BOLT11 invoices | required for invoice generation |
ENCRYPTION_SECRET |
Nostr secret key (hex or nsec) used to encrypt tenant NWC URLs at rest | required |
STRIPE_SECRET_KEY |
Stripe API secret key used for billing API operations | required |
STRIPE_WEBHOOK_SECRET |
Stripe webhook signing secret used to verify Stripe-Signature headers |
required |
STRIPE_PRICE_BASIC |
Stripe price ID (price_...) backing the Basic plan |
required for paid plans |
STRIPE_PRICE_GROWTH |
Stripe price ID (price_...) backing the Growth plan |
required for paid plans |
ROBOT_SECRET |
Robot Nostr secret key | required |
ROBOT_NAME |
Robot display name (kind 0) |
optional |
ROBOT_DESCRIPTION |
Robot description (kind 0) |
optional |
ROBOT_PICTURE |
Robot picture URL (kind 0) |
optional |
ROBOT_OUTBOX_RELAYS |
Comma-separated relays published as kind 10002 |
required |
ROBOT_INDEXER_RELAYS |
Comma-separated relays used for recipient relay discovery | required |
ROBOT_MESSAGING_RELAYS |
Comma-separated relays published as kind 10050 |
required |
Relay list env vars are comma-separated and trimmed. If a relay has no ws:// or wss:// scheme, wss:// is prepended.
Schema and Architecture
See spec for more details
API Routes
Most API routes are NIP-98 protected.
Public exceptions:
-
GET /plans -
GET /plans/:id -
POST /stripe/webhook(validated with Stripe signatures) -
GET /identity— get auth identity (pubkey,is_admin); side-effect-free -
GET /tenants— list tenants (admin) -
POST /tenants— idempotently ensure a tenant row exists for the current auth pubkey (creates Stripe customer + tenant on first call, returns existing tenant otherwise) -
GET /tenants/:pubkey— get tenant (admin or same tenant) -
PUT /tenants/:pubkey— update tenantnwc_url(admin or same tenant) -
GET /tenants/:pubkey/relays— list tenant relays (admin or same tenant) -
GET /relays— list relays (admin) -
POST /relays— create relay (admin or relay tenant) -
GET /relays/:id— get relay (admin or relay tenant) -
GET /relays/:id/members— list relay members from zooid (admin or relay tenant) -
PUT /relays/:id— update relay (admin or relay tenant) -
GET /relays/:id/activity— list relay activity (admin or relay tenant) -
POST /relays/:id/deactivate— deactivate relay (admin or relay tenant) -
POST /relays/:id/reactivate— reactivate relay (admin or relay tenant) -
GET /tenants/:pubkey/invoices— list tenant invoices (admin or same tenant) -
GET /invoices/:id— get invoice (admin or same tenant) -
GET /invoices/:id/bolt11— get invoice bolt11 (admin or same tenant) -
GET /tenants/:pubkey/stripe/session— create Stripe customer portal session (admin or same tenant)
API Auth Model
Caravel intentionally uses a session-style variant of NIP-98 for client-to-backend API auth.
- Frontend signs one kind
27235event withu = VITE_API_URLand caches that header for about 10 minutes. - Backend verifies event kind, signature, and that
ucontains configuredHOST. - Backend intentionally does not bind auth to exact request URL/method/query, and does not enforce payload hash, timestamp freshness window, or replay cache.
- Goal: reduce repeated wallet signing prompts and avoid cookie-based sessions.
- Tradeoff: this is weaker request-intent binding than strict NIP-98 semantics.