ncheta
v0.1.2
Published
Ncheta — webhook memory. SDK for Node.js.
Maintainers
Readme
ncheta
your webhooks just got memory.
ncheta (igbo: memory / remembrance) is a self-hostable webhook monitoring tool. it intercepts incoming webhooks, stores the raw request before your handler runs, then forwards it. if your handler crashes — the data is safe. fix the bug. replay. done.
no more "stripe sent it once and we missed it."
how it works
[Stripe] ──POST──> [ncheta :7000] ──stores──> [SQLite]
│
├── returns 200 to Stripe immediately
│
└── forwards to your handler in background
│
├── handler returns 200? done.
└── handler returns 500? retry with backoff.two ports. one binary.
- port 7000 — ingestion. webhook senders POST here.
- port 7001 — control API. list events, inspect payloads, trigger replays.
quickstart
rust binary
cargo build --release
./target/release/nchetanode sdk
npm install nchetaconst { Ncheta } = require("ncheta");
const ncheta = new Ncheta();
await ncheta.start();
// express middleware — captures everything, blocks nothing
app.post("/webhooks/stripe",
ncheta.watch({ endpointName: "stripe", targetUrl: "http://localhost:3000/webhooks/stripe" }),
(req, res) => {
// your handler. if this blows up, ncheta has the raw request.
res.json({ ok: true });
}
);config
all env vars. all optional. sane defaults.
| var | default | what it does |
|-----|---------|-------------|
| NCHETA_DB_URL | sqlite://./ncheta.db | database connection |
| NCHETA_INGESTION_PORT | 7000 | where webhooks land |
| NCHETA_CONTROL_PORT | 7001 | REST API + dashboard |
| NCHETA_MAX_BODY_SIZE | 524288 (512KB) | max request/response body |
| NCHETA_MAX_RETRIES | 5 | retries before giving up |
| NCHETA_EVENT_TTL_SECONDS | 604800 (7 days) | auto-cleanup after this |
api
ingestion (port 7000)
POST /in/{endpoint_name} → 200 { "received": true, "event_id": "..." }control (port 7001)
GET /health → 200 { "status": "ok" }
GET /endpoints → list all endpoints
POST /endpoints → register a new endpoint
GET /events → list events (filters: ?status=&endpoint_id=&limit=)
GET /events/{id} → full event detail with headers + body
GET /events/{id}/attempts → delivery attempt history
POST /events/{id}/replay → 202 re-deliver the eventreplay
# event failed? fix your handler, then:
curl -X POST http://localhost:7001/events/{id}/replay
# ncheta re-sends the exact same headers + body
# adds X-Ncheta-Replay: true so you know it's a replayretry backoff
failed deliveries retry automatically:
| attempt | delay | |---------|-------| | 1 | 30 seconds | | 2 | 5 minutes | | 3 | 30 minutes | | 4+ | 2 hours |
after max_retries failures → event goes to dead status. replay manually whenever you're ready.
architecture
- rust binary — axum + tokio + sqlx
- sqlite with WAL mode — fast writes, no external db needed
- zero mutex — all mutable state lives in the db
- single reqwest client — connection pooling, no fd exhaustion
- store-before-ack — db write completes before 200 is returned to sender
license
MIT
