-- 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. -- -- Foreign keys (named fk__): -- endpoint_secret.endpoint_id, subscription.endpoint_id -> endpoint CASCADE: -- an endpoint's secrets and subscriptions are its own configuration, so -- deleting the endpoint (webhooks.admin) removes them. -- delivery.endpoint_id, delivery_attempt.endpoint_id -> endpoint RESTRICT: -- delivery history keeps its endpoint; cancel and Delete Deliveries first. -- delivery.subscription_id -> subscription SET NULL: -- a delivery outlives the subscription that queued it. -- delivery_attempt.delivery_id -> delivery CASCADE: -- attempts belong to their delivery (Retention deletes both). -- Existing databases: run webhooks-foreign-keys-upgrade.sql. 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 CONSTRAINT fk_webhooks_endpoint_secret_endpoint_id REFERENCES webhooks_endpoint (id) ON DELETE CASCADE, 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 CONSTRAINT fk_webhooks_subscription_endpoint_id REFERENCES webhooks_endpoint (id) ON DELETE CASCADE, 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 CONSTRAINT fk_webhooks_delivery_endpoint_id REFERENCES webhooks_endpoint (id) ON DELETE RESTRICT, subscription_id INTEGER CONSTRAINT fk_webhooks_delivery_subscription_id REFERENCES webhooks_subscription (id) ON DELETE SET NULL, 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 INDEX webhooks_delivery_subscription_ix ON webhooks_delivery (subscription_id); 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 CONSTRAINT fk_webhooks_delivery_attempt_delivery_id REFERENCES webhooks_delivery (id) ON DELETE CASCADE, endpoint_id INTEGER NOT NULL CONSTRAINT fk_webhooks_delivery_attempt_endpoint_id REFERENCES webhooks_endpoint (id) ON DELETE RESTRICT, 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);