Files
qbit-webhooks/docs/ddl/webhooks-postgres.sql
T
james.maesandClaude Opus 5.5 6b56de589d feat!: qbit-webhooks 1.0 (signed, SSRF-safe, replica-safe outbound webhooks)
Rebuilds the qbit on QQQ 4.1.0-RC.4 per the assessment (QRun-IO/qqq#1105,
decisions D1-D8):

- webhooks_endpoint, webhooks_endpoint_secret, webhooks_subscription,
  webhooks_delivery and webhooks_delivery_attempt (snake_case, webhooks_
  prefix), permissions webhooks.view / manage / admin
- Standard Webhooks signing with per-endpoint secrets (AES-GCM or env
  references) and overlapping rotation
- SSRF-safe client: private, loopback, link-local and metadata ranges
  blocked, DNS pinned, no redirects, timeouts, size cap, admin-only
  internal allowlist (shared/ is an internal copy of QRun-IO/qqq#1109,
  replaced at RC.5)
- event sources: table insert/update/delete (record change listener),
  process completed/failed, ESB topics (own events skipped, CloudEvent
  id dedup), custom events and a channel API for other qbits
- delivery: unique attempt-row claims with leases (no double sends across
  replicas), retries with backoff, Retry-After, dead letters and Replay,
  serial mode, per-endpoint rate limits, health with probation and
  auto-disable, alerts through the Alerts SPI when present, retention
- subscriptions: record filters, record security, read permission check,
  detail levels, include and exclude fields, payload customizer hook
- app home dashboard, actions with the standard review/result screens,
  ESB / Quick Search / API integration hooks
- JaCoCo gate, Checkstyle in verify, Maven wrapper, default 4.1 profile,
  Postgres DDL in docs/ddl

The 0.x sources stay in the tree, excluded from the build, until their
removal is approved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:49:03 -05:00

144 lines
5.1 KiB
SQL

-- qbit-webhooks 1.0: the five webhooks_ tables (PostgreSQL).
--
-- Column names are the snake_case of the field names (WebhooksQBitConfig
-- snakeCaseColumnNames, the default). Hosts that configure securityFields add
-- those columns to webhooks_endpoint, webhooks_subscription and
-- webhooks_delivery. The unique keys matter: webhooks_delivery.dedup_key stops
-- an event being queued twice, and (delivery_id, attempt_number) on
-- webhooks_delivery_attempt is the claim that stops two replicas sending the
-- same attempt.
CREATE TABLE webhooks_endpoint
(
id SERIAL PRIMARY KEY,
create_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
modify_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
name VARCHAR(100) NOT NULL,
description TEXT,
url VARCHAR(500) NOT NULL,
active_status VARCHAR(20) DEFAULT 'ACTIVE',
health_status VARCHAR(20) DEFAULT 'HEALTHY',
serial_delivery BOOLEAN DEFAULT FALSE,
max_per_minute INTEGER,
timeout_seconds INTEGER,
allow_unsigned BOOLEAN DEFAULT FALSE,
allow_internal_targets BOOLEAN DEFAULT FALSE,
internal_allowlist TEXT,
consecutive_failures INTEGER DEFAULT 0,
probation_failures INTEGER DEFAULT 0,
last_success_date TIMESTAMP WITH TIME ZONE,
last_failure_date TIMESTAMP WITH TIME ZONE,
last_failure_message VARCHAR(500),
unhealthy_since_date TIMESTAMP WITH TIME ZONE,
auto_disabled_date TIMESTAMP WITH TIME ZONE,
CONSTRAINT webhooks_endpoint_name_uk UNIQUE (name)
);
CREATE TABLE webhooks_endpoint_secret
(
id SERIAL PRIMARY KEY,
create_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
modify_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
endpoint_id INTEGER NOT NULL REFERENCES webhooks_endpoint (id),
status VARCHAR(20) DEFAULT 'ACTIVE',
source VARCHAR(20) NOT NULL,
secret_ref VARCHAR(100),
encrypted_secret TEXT,
fingerprint VARCHAR(20),
expires_date TIMESTAMP WITH TIME ZONE,
retired_date TIMESTAMP WITH TIME ZONE,
note VARCHAR(250)
);
CREATE INDEX webhooks_endpoint_secret_endpoint_ix ON webhooks_endpoint_secret (endpoint_id, status);
CREATE TABLE webhooks_subscription
(
id SERIAL PRIMARY KEY,
create_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
modify_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
endpoint_id INTEGER NOT NULL REFERENCES webhooks_endpoint (id),
event_type_name VARCHAR(200) NOT NULL,
active_status VARCHAR(20) DEFAULT 'ACTIVE',
description VARCHAR(250),
filter_json TEXT,
detail_level VARCHAR(20) DEFAULT 'FULL',
include_fields VARCHAR(1000),
exclude_fields VARCHAR(1000),
api_name VARCHAR(100),
api_version VARCHAR(50)
);
CREATE INDEX webhooks_subscription_type_ix ON webhooks_subscription (event_type_name, active_status);
CREATE INDEX webhooks_subscription_endpoint_ix ON webhooks_subscription (endpoint_id);
CREATE TABLE webhooks_delivery
(
id SERIAL PRIMARY KEY,
create_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
modify_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
event_id VARCHAR(40) NOT NULL,
event_type_name VARCHAR(200) NOT NULL,
endpoint_id INTEGER NOT NULL,
subscription_id INTEGER,
source VARCHAR(20),
source_table VARCHAR(100),
source_record_id VARCHAR(100),
source_event_id VARCHAR(100),
dedup_key VARCHAR(250) NOT NULL,
status VARCHAR(20) NOT NULL,
attempt_count INTEGER DEFAULT 0,
cycle_attempt_count INTEGER DEFAULT 0,
replay_count INTEGER DEFAULT 0,
occurred_date TIMESTAMP WITH TIME ZONE,
next_attempt_date TIMESTAMP WITH TIME ZONE,
lease_expires_date TIMESTAMP WITH TIME ZONE,
last_http_status INTEGER,
last_error VARCHAR(500),
delivered_date TIMESTAMP WITH TIME ZONE,
dead_letter_date TIMESTAMP WITH TIME ZONE,
payload_bytes INTEGER,
payload TEXT,
CONSTRAINT webhooks_delivery_dedup_key_uk UNIQUE (dedup_key),
CONSTRAINT webhooks_delivery_event_id_uk UNIQUE (event_id)
);
CREATE INDEX webhooks_delivery_due_ix ON webhooks_delivery (endpoint_id, status, next_attempt_date);
CREATE INDEX webhooks_delivery_status_ix ON webhooks_delivery (status, modify_date);
CREATE INDEX webhooks_delivery_delivered_ix ON webhooks_delivery (delivered_date);
CREATE TABLE webhooks_delivery_attempt
(
id SERIAL PRIMARY KEY,
create_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
modify_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
delivery_id INTEGER NOT NULL,
endpoint_id INTEGER NOT NULL,
attempt_number INTEGER NOT NULL,
outcome VARCHAR(20) NOT NULL,
claimed_by VARCHAR(100),
lease_expires_date TIMESTAMP WITH TIME ZONE,
start_date TIMESTAMP WITH TIME ZONE,
end_date TIMESTAMP WITH TIME ZONE,
duration_millis INTEGER,
http_status INTEGER,
response_excerpt TEXT,
error_message VARCHAR(1000),
remote_address VARCHAR(64),
request_url VARCHAR(500),
signed_with VARCHAR(100),
CONSTRAINT webhooks_delivery_attempt_uk UNIQUE (delivery_id, attempt_number)
);
CREATE INDEX webhooks_delivery_attempt_endpoint_ix ON webhooks_delivery_attempt (endpoint_id, start_date);
CREATE INDEX webhooks_delivery_attempt_start_ix ON webhooks_delivery_attempt (start_date);