All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog.
- Clippy
too_many_argumentswarnings with-Dwarnings(CI was failing) - Doc list indentation in
db.rs - Benchmark
DatabaseConfigmissing D1 fields rustls-webpkiRUSTSEC-2026-0049 (updated to 0.103.10)aws-lc-sysRUSTSEC-2026-0044/0048 ignored (requires newer Rust than MSRV)cargo fmtformatting inconsistencies
- Pull-mode queue delivery: Consumer-side polling via
GET /api/queues/{name}/messageswith long-polling,POST /ack,POST /nack. Configurable visibility timeout, per-queue API key auth. Events matching a queue create jobs consumed by polling instead of push. - Cloudflare D1 database adapter: HTTP-based adapter for Cloudflare D1. Supports REST API mode and Outbound Workers proxy mode. Enables
wrangler deployfor serverless webhook processing. - At-least-once delivery guarantee: Event and all jobs are now inserted in a single database transaction. If qhook crashes mid-insert, the DB rolls back and the webhook sender retries.
- Database backpressure: When 3+ consecutive DB errors occur, webhook/event/SNS handlers return
503 Service UnavailablewithRetry-After: 30. Auto-recovers when DB succeeds. Health endpoint also recovers the flag if the worker crashes. - Queue management CLI:
qhook queues list,inspect,drain,dlq,retry,peekcommands for pull-mode queue operations. qhook statuscommand: One-command overview — events by source, jobs by status, handler stats, queue depths, workflow counts.- CLI remote mode:
--remote <URL> --token <TOKEN>flags (orQHOOK_REMOTE_URL/QHOOK_API_TOKENenv vars) to manage deployed instances via HTTP API instead of local DB. X-Qhook-Delivery-IDheader: DeterministicSHA256(event_id:handler)on every delivery for handler-side deduplication.- Event TTL:
delivery.event_ttlconfig (e.g.,168h) auto-expires available/retryable jobs older than the threshold. - Retry jitter: ±20% random jitter on all exponential backoff to prevent thundering herd across instances.
- Bulk retry API:
POST /api/jobs/retry?status=dead&handler=xxxfor batch retry operations. - Operational webhooks: New alert events —
circuit_opened,workflow_failed,db_unhealthy,event_ttl_expired. - Structured validation errors: API errors now return
{"code", "message", "details"}format with field-level paths. - Outbound endpoint verification: Challenge-response ownership check on endpoint creation (
skip_endpoint_verificationconfig for dev). - Tracing spans:
event_id/source/event_typeonprocess_event,job_id/handleron delivery for structured log correlation. - Production warnings in
qhook validate: Warns about missingapi.auth_token,allow_private_urls, and queueapi_key. - Scaling guide:
docs/guides/scaling.md— D1 vs Postgres decision framework with cost breakdowns. - Local-to-production guide:
docs/guides/local-to-production.md— environment overlays, DB migration, security checklist. - Pull-mode queue guide:
docs/guides/pull-mode-queues.md— API reference, config, consumer snippets in Python/TypeScript/Go/Ruby. - Cloudflare deploy guide:
docs/deploy/cloudflare.md— Containers + D1 setup. - Stripe pull example:
examples/stripe-pull/— Python and TypeScript consumer examples. - Benchmark scripts:
scripts/bench-cloudflare.sh, k6 scripts for ingestion, delivery, pull-mode, and D1 scenarios.
- README rewritten: Progressive disclosure — core message ("The missing layer between webhooks and your app") with one use case. Workflows, pull-mode, outbound moved to docs.
/healthendpoint: Now includesdead_jobscount and returns"degraded"status when DLQ has items.- Stale recovery excludes queue jobs: Push stale recovery no longer touches pull-mode queue messages (separate visibility timeout recovery).
fetch_available_jobsexcludes queue jobs: Push worker ignoresqueue/*handler prefix.
- MySQL/SQLite parallel branch completion race: Wrapped UPDATE + SELECT in transaction to prevent duplicate workflow step execution.
- Nack TOCTOU race: Added
AND status = 'running'guard to nack UPDATE queries. delete_jobs_by_handlertransaction safety: Wrapped in explicit transaction to prevent orphaned records.retry_jobresets attempt counter: Prevents immediate re-dead after retry.- SNS cert cache TTL cleanup:
retain()expired entries on insert. - Alert bounded channel: Changed from unbounded to capacity 1000 with graceful overflow handling.
- D1 client timeout: Set explicit 10s timeout to prevent indefinite hangs.
thiserrordependency: Unused, removed.
- Gzip compression: HTTP responses are now gzip-compressed via
tower-httpCompressionLayer, reducing bandwidth for API and metrics responses. - Full JSON Schema validation: Replaced manual schema validation with the
jsonschemacrate (Draft 2020-12). Now supportsenum,pattern,minLength,maxLength, and all standard JSON Schema keywords. - Local replay mode:
qhook replay-local <file.jsonl>replays exported events (fromqhook export events) to a running server. Supports--target,--token, and stdin (-). - Twilio webhook verification:
verify: twiliochecksX-Twilio-Signatureheader (HMAC-SHA1, base64-encoded). - Paddle webhook verification:
verify: paddlechecksPaddle-Signatureheader (ts=...;h1=...format, HMAC-SHA256) with 5-minute replay protection. - MySQL support:
database.driver: mysqlfor MySQL/MariaDB backends via sqlx AnyPool. AdaptsFOR UPDATE SKIP LOCKED,INSERT IGNORE, andVARCHAR(255)primary keys for MySQL compatibility. - llms.txt: AI agent discoverability file at
docs/llms.txtfollowing the llms.txt specification. - Compliance documentation: PCI DSS 4.0 and SOC 2 compliance framing guide (
docs/guides/compliance.md) with audit trail mapping tables. - proptest property-based tests: 29 property-based tests covering config parsing robustness, retry backoff invariants, filter evaluation safety, and ULID generation properties.
- insta snapshot tests: 21 snapshot tests for config validation error messages, default config template, Prometheus metrics format, and API response formats.
- Event inspection API:
GET /api/events,GET /api/events/{id}/jobs,GET /api/jobs,GET /api/jobs/{id}/attemptswith filtering, cursor-based pagination, andhas_moresupport. - Filtered replay:
replay-localnow supports--source,--event-type,--since,--until,--statusfilters. Event type supports prefix matching with*. - Benchmark suite: criterion-based benchmarks for config parsing, filter evaluation, ULID generation, signature verification, and SQLite event insertion.
- MCP server: TypeScript MCP server (
mcp-server/) wrapping qhook's REST API for AI agent integration (Claude Code, Claude Desktop).
- Per-destination rate limiting: Switched from semaphore-hold to GCRA-based rate limiting via the
governorcrate. Provides smoother, more accurate per-handler rate limiting.
- Standard Webhooks compliance: Outbound webhooks now use
webhook-id,webhook-timestamp,webhook-signatureheaders per the Standard Webhooks spec. Signed content format:{msg_id}.{timestamp}.{body}with base64 output andv1,prefix. - Retry-After header support: Respects
Retry-Afterfrom downstream 429/503 responses to override exponential backoff delay (capped at 86400s). - DB migration versioning: Migrations tracked in
_migrationstable with auto-detection of pre-existing databases. Enables safe schema upgrades across versions. - Configurable worker concurrency:
worker.max_concurrencyandworker.batch_sizeconfig fields replace hardcoded constants (both default to 10). - CI code coverage:
cargo-llvm-covintegrated in CI pipeline with LCOV output. - Cost comparison documentation: Added infrastructure cost comparison vs Svix, Hookdeck, and AWS Step Functions in
docs/why-qhook.md. - Standard Webhooks inbound verification:
verify: standard-webhooksfor providers using the Standard Webhooks spec (Clerk, Resend, Lemon Squeezy, Orb). Includes 5-minute replay protection and multi-signature support for key rotation. - Linear webhook verification:
verify: linearchecksLinear-Signatureheader (HMAC-SHA256). - IP allowlisting:
allowed_ipsfield on sources restricts webhook reception to specific IPs/CIDRs. Supports IPv4, IPv6, and proxy-aware IP extraction.
- Outbound webhook headers changed from
X-Qhook-Signature/X-Qhook-Timestampto Standard Webhooks format (outbound webhooks were added in v0.4.1, minimal impact).
- Outbound webhooks: Send webhooks to your customers with per-endpoint HMAC-SHA256 signatures. New
type: outboundsource with dynamic endpoint management via REST API.POST/GET /api/outbound/endpoints— create and list customer endpointsGET/PUT/DELETE /api/outbound/endpoints/:id— manage individual endpointsPOST /api/outbound/endpoints/:id/rotate-secret— rotate signing secretPOST/GET/DELETE /api/outbound/endpoints/:id/subscriptions— event type subscriptions- Per-endpoint signing secret (
whsec_prefix) withX-Qhook-Signature: v1=<hmac>andX-Qhook-Timestampheaders - Wildcard (
*) subscriptions, fan-out to multiple endpoints, disable/enable toggle - Reuses existing retry, DLQ, and circuit breaker infrastructure
- Example:
examples/outbound-webhook/— customer webhook receiver with signature verification - 10 outbound E2E tests + 1 full lifecycle scenario test (endpoint → subscribe → deliver → verify signature → disable → rotate secret)
- TypeScript and Python SDK generation from OpenAPI spec (
sdks/generate.sh)
- JSON response for webhook and event endpoints — returns
event_id,jobs_created, andduplicatestatus - Management API:
GET /api/events/:idreturns event details with associated jobs and workflow runs - Management API:
GET /api/jobs/:idreturns job details with optional delivery attempts (?include_attempts=true) cargo-denysecurity checks (advisories, licenses, bans, sources) in CI and weekly schedule- Rust integration tests replacing shell-based E2E tests (30 tests: 12 e2e + 12 workflow + 6 scenarios)
POST /webhooks/:sourcenow returns JSON instead of plain text (breaking change for clients parsing text responses)- Event endpoint is now
POST /events/{source}/{event_type}— the oldPOST /events/{event_type}route was removed POST /events/:source/:typenow returns JSON{"event_id": "...", "jobs_created": N}instead of plain text- Updated product positioning to "lightweight workflow engine"
qhook sendCLI: Send test events to a running server without curl. Supports inline JSON, file input, and auto-detects source type/port from config.qhook doctorCLI: Pre-production readiness check. Validates config, database connectivity, handler/workflow endpoint reachability, and security settings.- Echo endpoint: Built-in
/_echoendpoint returns request headers and body as JSON. Eliminates the need for a mock server during development. - Local development guide: New
docs/guides/local-development.mdcovering echo endpoint, test events, tunnels (GitHub CLI, LocalStack, cloudflared). - Remote config loading:
qhook start -c s3://...,-c gs://...,-c az://..., or-c https://...loads config from AWS S3, GCS, Azure Blob, or HTTP. Polls every 30s for changes (ETag-based). Invalid config is rejected with current config preserved and logged. Public endpoints only (private bucket support tracked in #26). qhook inspectCLI: Show an event's full lifecycle — payload, matched jobs with status/attempts, workflow runs. Debug event flow in one command.qhook send --dry-run: Show which handlers and workflows would match an event without creating jobs.qhook init --template: Scaffold config from templates (github,stripe,sns,cron).- JSON Schema:
docs/schema.jsonfor editor autocomplete and validation. Add# yaml-language-server: $schema=https://totte-dev.github.io/qhook/schema.jsontoqhook.yaml. - Database schema guide:
docs/guides/database-schema.md— full table/column/index reference. - Config overlay (
--env):qhook start --env productionmergesqhook.production.yamlon top ofqhook.yaml. Also loads.env.productionfor environment variables. SupportsQHOOK_ENVenvironment variable.qhook initnow generatesqhook.local.yamlalongsideqhook.yaml. qhook tail: Real-time event and job stream in the terminal. Filter by--sourceor--status. Color-coded output.qhook export events: Export events as JSONL for portability between environments. Supports--source,--event-type,--since,--untilfilters.
- Dependency reduction: Replaced
regexwithregex-lite(smaller binary, no Unicode tables). Trimmedtokiofeatures fromfullto only required features (rt-multi-thread,macros,sync,signal,net,time). ~1MB binary size reduction. - OTLP exporter: Switched from gRPC (tonic) to HTTP JSON transport. Set
OTEL_EXPORTER_OTLP_ENDPOINTto an HTTP endpoint.
- gRPC output: Removed
type: grpchandler support andtonic/prostdependencies. gRPC added significant binary size (~3MB) for limited use cases — HTTP handlers with Envoy or gRPC-gateway cover the same scenarios. If demand exists, gRPC support will be available in the Cloud version. - Helm chart: Removed
charts/qhook/. qhook is a single-binary application and doesn't benefit from Helm's complexity. For Kubernetes, use a simple Deployment manifest with the Docker image directly.
- Circuit breaker: Per-handler circuit breaker. Opens after 5 consecutive failures, closes after 60s cooldown with half-open probe. New metrics:
qhook_circuit_breaker_opened_total,qhook_circuit_breaker_rejected_total. - OpenTelemetry tracing: Optional
otelfeature flag (cargo build --features otel). Exports traces via OTLP whenOTEL_EXPORTER_OTLP_ENDPOINTis set. Falls back to standard tracing otherwise. - Event replay CLI:
qhook events replayre-creates jobs for historical events. Supports--source,--event-type,--since,--untilfilters. - Helm chart: Kubernetes deployment via
charts/qhook/. Includes Deployment, Service, ConfigMap, Ingress, PVC, HPA, and ServiceAccount. - SIGHUP config diff: SIGHUP now logs added/removed/changed sources, handlers, and workflows. Warns about changes requiring restart (port, database driver).
- Advanced filter operators:
contains(substring + array membership),starts_with,ends_with,matches(regex),exists, andnot(negation). - Event schema validation:
schemafield on sources for lightweight JSON Schema validation (type,required,properties). Rejects non-conforming events with 400. - Sub-workflow step:
type: workflowstep invokes another workflow as a child. Parent workflow resumes after sub-workflow completes. Supports nesting.
- Performance: Extracted
format_now()/format_dt()helpers to eliminate 15+ repeated datetime formatting calls in DB layer. Optimized Stripe HMAC to avoid intermediate String allocation. Pre-allocated SNS signature string builder. Replacedfrom_utf8+to_string()withString::from_utf8in HTTP handlers.
- HTTP method specification:
methodfield on handlers, workflow steps, and parallel branches. SupportsGET,POST(default),PUT,PATCH,DELETE. GET requests omit the body. - Cron triggers: New
cronsource type withschedule(cron expression) and optionaltimezone. Firescron.tickevents on schedule, matching handlers and workflows.
- Test coverage improvements: 14 gap-coverage tests added, 4 redundant tests removed (174 total unit tests).
- Workflow engine: Event-driven multi-step pipelines defined in YAML.
- Sequential workflows with response chaining (step N's response → step N+1's input).
- Data flow control:
input(transform before call),result_path(merge response),output(transform after). - Per-step retry with error type matching (
timeout,5xx,4xx,network,all). catchblocks for error routing to named fallback steps after retries exhausted.on_failure: continueto proceed to next step with error info on failure.end: trueto terminate workflow early.- Choice step (
type: choice): conditional routing withwhenconditions anddefaultfallback. Reuses filter syntax (==,!=,>=,>,<=,<,in). - Parallel step (
type: parallel): concurrent branch execution. Results merged as object keyed by branch name. - Map step (
type: map): iterate over array items in payload. Results collected as array. - Wait step (
type: wait): pause workflow for a fixedsecondsdelay or until a dynamictimestamp_pathfrom the payload. No HTTP call — next step's job is scheduled with a futurescheduled_at. - Callback step (
type: callback): pause workflow and wait for an external system to callPOST /callback/:tokenwith a JSON body to resume. Optionalcallback_timeoutto expire waiting callbacks. - Workflow timeout:
timeoutfield on workflow config sets an overall time limit. If exceeded, subsequent steps are skipped and the workflow is marked as failed. - Workflows and handlers coexist — same event can trigger both.
- Workflow metrics:
qhook_workflow_runs_total(by workflow + status),qhook_workflow_steps_completed_total(by workflow),qhook_callbacks_received_total,qhook_callbacks_expired_total. - Filter operators: Added
>=,>,<=,<numeric comparisons (handler filters + choice conditions). - CLI:
workflow-runs listandworkflow-runs redrivecommands. - DB:
workflow_runstable with parallel tracking (parallel_count,parallel_completed). - Config:
workflowssection in YAML with full validation (step name uniqueness, catch goto targets, branch names, source references). - Example:
examples/workflow/— order processing pipeline with catch routing. - Docs: Workflow guide at
docs/guides/workflows.md. - Custom headers:
headersfield on handlers, workflow steps, and parallel branches for authenticated API calls (e.g.,Authorization: Bearer ${TOKEN}). - Callback URL notification: Callback steps with a
urlfield POST the callback token to the external service before waiting. - Workflow input parameters:
paramsfield on workflows for runtime payload validation (type checking: string/number/boolean/object/array, required/optional). - Signature verification: PagerDuty (
X-PagerDuty-Signature, HMAC-SHA256), Grafana (X-Grafana-Alerting-Signature, HMAC-SHA256), Terraform Cloud (X-TFE-Notification-Signature, HMAC-SHA512), GitLab (X-Gitlab-Token, constant-time comparison). - Examples:
examples/tenant-provision/(params + headers + rollback),examples/alert-remediation/(PagerDuty + choice + wait + escalation).
- SSRF protection: Handler/workflow URLs pointing to private/loopback IPs are now rejected by default. Set
server.allow_private_urls: truefor local development. - Metrics endpoint authentication: Optional
api.metrics_auth_tokento protect the/metricsendpoint with a bearer token. - DB credential redaction: Database connection URLs are redacted (credentials removed) before logging on connection errors.
- Proxy-aware rate limiting:
server.trust_proxyenables extraction of client IP fromX-Forwarded-For/X-Real-IPheaders when behind a reverse proxy. Requests with no determinable IP are now denied instead of bypassing the rate limiter. - Parallel branch race condition: Fixed potential double-execution of the next workflow step when parallel branches complete concurrently, by using atomic
UPDATE ... RETURNINGon Postgres.
Initial release.
- Webhook receive, queue, and retry with exponential backoff.
- Signature verification: Stripe (
t=...,v1=...), GitHub (sha256=...), Shopify (Base64 HMAC), generic HMAC. - Idempotency via configurable JSONPath dedup key.
- Dead Letter Queue for exhausted jobs.
- SQLite and Postgres support via sqlx AnyPool.
- CloudEvents: Binary mode (
ce-typeheader) and structured mode (application/cloudevents+json).ce-*headers forwarded to handlers. - AWS SNS: Automatic subscription confirmation, envelope unwrapping, X.509 signature verification (SHA1/SHA256).
skip_verifyoption for LocalStack testing.
- Event filtering:
handler.filterwith JSONPath-like syntax (==,!=,in [a, b], truthy). - Payload transformation:
handler.transformwith{{$.path}}placeholders. Applied at delivery time, original payload preserved. - gRPC output:
handler.type: grpcwithqhook.v1.EventReceiver/Deliverunary RPC. Proto file included.
- Concurrent delivery (max 10 parallel).
- Adaptive polling (50ms busy / 1s idle).
- Stale job recovery (5min threshold).
- Auto cleanup (72h retention).
- Graceful shutdown (SIGTERM/SIGINT + drain with configurable timeout).
- Postgres
SELECT FOR UPDATE SKIP LOCKEDfor multi-instance deployments. - Prometheus metrics (
/metrics) with per-source and per-handler labels. - Health check (
/health) with queue depth. - Per-handler rate limiting.
- Per-IP rate limiting (
server.ip_rate_limit).
- Stripe replay protection (5min signature timestamp).
- Request body size limit (default 1MB).
- Inbound concurrency limit (default 100).
- Constant-time auth token comparison.
- Security headers (nosniff, DENY, no-store).
- SNS cert URL domain validation.
- Transform JSON injection prevention.
- Alert system (Slack, Discord, generic webhook) on DLQ and verification failures.
- Structured JSON logging (
QHOOK_LOG_FORMAT=json). - Slow query logging (>100ms).
- SIGHUP config validation (dry-run reload).
- Configurable DB pool size, stale threshold, retention hours, drain timeout.
- Commands:
init,start,validate,jobs list/retry,events list.
- Docker image and Compose files.
- Deployment guides: AWS (ECS Fargate / EC2), Railway, Fly.io, Render.
- GitHub Actions CI (fmt + clippy + test + E2E).
- Documentation site (GitHub Pages).
- Examples: quickstart, github-webhook, filter-transform, stripe-checkout.