Skip to content

WebhookEngine v0.1.6

Choose a tag to compare

@voyvodka voyvodka released this 08 May 11:17
· 64 commits to main since this release
4fb119a

WebhookEngine v0.1.6

Feature & polish cut covering eight new capabilities (per-resource overrides, IP allowlist, audit log, endpoint test webhook, SignalR endpoint health, validate-time URL guard), three rounds of dashboard polish (a11y, UX, TanStack Query data layer), three reviewer-finding fixes (transient DNS retry, cascade delete, polling debounce), and a backend correctness pass on the IP allowlist matcher, application rate-limiter sweep, and endpoint health tracker. No breaking API changes — the v1 route prefix and Standard Webhooks signature surface are preserved. Test count moved from 211 to 215.

Features / Fixes / Changes

Added

  • Endpoint test webhook (F1): POST /api/v1/dashboard/endpoints/{id}/test fires a customizable, fully-signed webhook to the endpoint URL without enqueueing a real Message, and returns the receiver's response plus the exact request that was sent. Dashboard endpoint editor carries a Send test drawer.
  • Per-application rate-limit override (F6): Application.RateLimitPerSecond 1-second sliding-window override complements the per-endpoint per-minute gate; idle-evicted at 15 minutes.
  • Per-application retention overrides (F3): Application.RetentionDeliveredDays and RetentionDeadLetterDays override WebhookEngine:Retention defaults per tenant; the cleanup worker partitions its sweep accordingly.
  • Per-event-type idempotency window (F4): EventType.IdempotencyWindowMinutes overrides the per-app default for tighter or looser dedupe per event family.
  • Per-endpoint IP allowlist (F8): Endpoint.AllowedIpsJson carries a CIDR positive-list (IPv4 + IPv6); deliveries only proceed when every resolved address sits inside at least one allowed CIDR.
  • Append-only audit log (F9): Admin actions across applications, endpoints, event types, replay, retry, and rotate-key write a forensic row to the new audit_logs table with before / after snapshots and request_id. GET /api/v1/dashboard/audit exposes a paginated, filterable view. The table holds no FKs — rows survive cascades.
  • SignalR endpoint health channel (F7): DeliveryHub broadcasts EndpointHealthChanged(endpointId, status, circuitState, consecutiveFailures, cooldownUntilUtc) whenever EndpointHealthTracker mutates an endpoint's circuit or visible status. Dashboard EndpointsPage consumes the event and invalidates its cache.
  • TanStack Query dashboard data layer (F12): Every dashboard page (Dashboard, Messages, Applications, Endpoints, EventTypes, DeliveryLog) routes server data through useQuery / useMutation. Manual setInterval polling and the smart-debounce shim are gone in favor of cache-aware refetching driven by SignalR invalidation. EndpointsPage initial chunk drops from ~1.5 MB to ~20 kB (CodeMirror lazy-loaded).

Changed

  • Validator chain rejects private-IP endpoint URLs at create / update (F2). Same SSRF rules already enforced at delivery (ConnectCallback) now run at validate time too — a misconfigured URL is rejected before the row exists.
  • Modal a11y, mobile, dvh (DPR-1 + DPR-3): role="dialog", ARIA wiring, focus trap, max-h-[85dvh], mobile filter md:grid-cols-3, URL field server-side errors as field-scoped messages, payload editor errors split out, SignalR Live / Offline pill in the EndpointsPage header.
  • Dashboard consolidation (DPR-2): Shared StatusBadge, inputClasses, and CodeMirror editorTheme. CodeMirror is React.lazy() — the 1.5 MB chunk loads only when the endpoint editor mounts. parseError returns an ApiError with optional fieldErrors.
  • Backend polish (R2 + R4 + R5): IpAllowlistMatcher.AllAddressesAllowed short-circuits empty allowlists before the empty-resolution deny branch (load-bearing ordering). ApplicationRateLimiter._lastSweepTicks is Volatile.Read + CAS so torn 32-bit reads can't spawn back-to-back sweeps. EndpointHealthTracker.WithEndpointLockAsync no longer double-fetches the endpoint row.
  • Dependency refresh: Backend — Scalar.AspNetCore 2.14.10 → 2.14.11, coverlet.collector 8.0.1 → 10.0.0. Frontend — react / react-dom / react-is 19.2.5 → 19.2.6, react-router 7.14.2 → 7.15.0, @codemirror/view 6.41.1 → 6.42.1, vite 8.0.10 → 8.0.11, plus @tanstack/react-query 5.100.9 (drives F12).

Fixed

  • Transient DNS failures retry within budget (R1): SocketException / ArgumentException from the IP-allowlist resolution now route through MarkFailedForRetryAsync instead of dead-lettering on first miss; only after the retry budget is exhausted does the message dead-letter.
  • Application / endpoint deletion cascades to bound messages (R3): Message → Application and Message → Endpoint foreign keys carry ON DELETE CASCADE. Migration 20260508081704_CascadeMessageDeleteOnAppAndEndpoint is hand-written SQL because EF doesn't diff OnDelete changes.
  • Modal focus trap, awaited refetches, SignalR cache invalidation on reconnect (DPR-1): Skeleton loaders carry aria-busy="true"; mutating actions await their refetch before closing modals; useDeliveryFeed.onreconnected resets lastHealthChange so a stale event from before the disconnect doesn't double-fire.
  • Smart-debounced dashboard polling (R6) folded into F12: SignalR events now invalidate cache keys; TanStack Query handles the refetch cadence.

Security

  • Endpoint URL DNS resolution at validator chain (F2): Same private-IP rules already enforced at delivery time now run at create / update — a misconfigured URL is refused before the row exists.
  • Per-endpoint IP allowlist (F8): Opt-in CIDR positive-list at delivery time; transient resolver failures retry within the message's normal budget (R1) so flaky DNS doesn't cascade into dead-letter floods.
  • Append-only audit log (F9): Forensic trail of admin actions; rows survive cascades for post-incident reconstruction.

Quick Start

docker pull voyvodka/webhook-engine:0.1.6
git clone https://github.com/voyvodka/webhook-engine.git
cd webhook-engine
docker compose -f docker/docker-compose.yml up -d

Dashboard at http://localhost:5100 — login admin@example.com / changeme (reset before exposing publicly).

Links