Skip to content

Architecture and Security

Diego Gutierrez edited this page Sep 23, 2026 · 1 revision

Home · Previous: API and Configuration · Next: Troubleshooting and Contributing

From capture to replay

Sender → POST /in/{token} → size check + header redaction → SQLite commit → 201
Workbench → bearer-authenticated API → stored events and history
Manual replay → persist pending attempt → configured receiver → persist outcome

Events and attempts are separate records. Replaying does not replace the original event or erase earlier failures.

Design decisions

Decision Benefit Boundary
SQLite with WAL No extra database service for local use Small synchronous operations can block the API loop; no production throughput claim
Commit before capture acknowledgement An accepted event survives an app restart Backups and infrastructure durability remain the operator's concern
Persist a pending attempt before sending Record unfinished work across restart Remote side effects can happen before a final outcome is recorded
One application process Simple local lifecycle Multiple processes can misclassify each other's pending attempts
Named destinations API callers select approved server configuration Operator-controlled DNS and URLs remain trusted inputs
Plain HTML/CSS/JavaScript UI Small package focused on backend behavior Manual refresh and loaded-page filtering

Attempt states

A replay starts as pending and finishes as delivered for a destination 2xx, or failed for another status or a network error. An unfinished attempt can become interrupted after cancellation or on startup recovery. Interrupted does not mean the receiver did nothing.

The built-in demo uses in-process ASGI transport. Its latency is not a network benchmark. Replay and Idempotency shows real local HTTP delivery and why receiver deduplication matters.

Module map

Location under webhook_lab/ Responsibility
config.py Environment and destination validation
store.py Schema, capacity and persisted event/attempt lifecycle
replay.py One bounded outgoing HTTP attempt
app.py Routes, auth, limits and application lifecycle
static/ Event inspector UI

Security boundaries

This build is a local, single-user development tool. Keep its loopback binding; it is not ready for public hosting.

  • The inbox URL allows capture. The separate administrator token grants inspection and replay. Keep both private.
  • A diagnostic header allowlist retains selected values; other values become [redacted] before storage. Query strings are not stored.
  • Bodies are unchanged and unencrypted. Header redaction does not redact payloads. Use synthetic data for demos and screenshots.
  • Replay preserves body/content type, but not captured credentials, cookies or provider signatures. Response bodies are not stored.
  • Redirects and environment proxies are disabled. Private destination addresses remain allowed for local development; this is not public multi-tenant SSRF isolation.
  • There is an event cap, but no attempt-count cap or automatic retention yet.
  • UI previews use text nodes and raw bodies download as attachments. The optional Swagger documentation uses FastAPI's standard external assets.

What comes next

The roadmap proposes migrations, PostgreSQL and durable jobs with leases before multi-process background delivery. Those features have not shipped.

See the architecture document for the detailed diagram and security policy for private vulnerability reporting guidance.