Skip to content

About

QBit for webhook handling. Inbound and outbound webhook management.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

QBit Webhooks

Signed, SSRF-safe outbound webhooks for QQQ applications. Admins add endpoints and subscribe them to event types: table inserts, updates and deletes (in-process or from the ESB), process completions and failures, and custom domain events. The qbit queues one delivery per event per endpoint in the same transaction as the change. It then signs each delivery in the Standard Webhooks format and sends it with retries, backoff, dead letters and endpoint health. Every row is a normal QQQ table, and the app has a home dashboard.

Design: QRun-IO/qqq#1105 (docs/specs/2026-10-12-qbit-webhooks-assessment.md), and docs/DESIGN.md for what 1.0 implements. Signing and secret generation use QQQ core directly; the local safe HTTP client remains pending a separate migration. Both primitives are available in QQQ core from 4.1.0-RC.5 (QRun-IO/qqq#1109).

Requirements

  • Java 21
  • QQQ 4.1.0-RC.8 or later (qqq-backend-core). Two modules are optional:
    • qqq-middleware-api, for payloads rendered through a QQQ API version.
    • qqq-esb, for ESB topics as an event source.
  • A SQL backend for the five tables (DDL in docs/ddl/webhooks-postgres.sql), and a QQQ scheduler for delivery.

Install

<dependency>
   <groupId>com.kingsrook.qbits</groupId>
   <artifactId>qbit-webhooks</artifactId>
   <version>1.0.0</version>
</dependency>
new WebhooksQBitProducer()
   .withQBitConfig(new WebhooksQBitConfig()
      .withBackendName("rdbms")
      .withSchedulerName("scheduler")
      .withSourceName("myapp")                       // events say qqq://myapp/...
      .withTableEventSources(List.of("order", "customer"))
      .withProcessEventSources(List.of("shipOrder")))
   .produce(qInstance);

// optional: also consume ESB topics (events from other services)
WebhooksEsbSource.configure(qInstance);

Set WEBHOOKS_SECRET_KEY to the base64 of 32 random bytes, so the qbit can generate signing secrets and store them encrypted. Without it, use environment-reference secrets only.

Tables

Table What it holds In the nav
webhooks_endpoint URL, status (Active, Paused, Disabled), health (Healthy, Probation, Unhealthy), in-order delivery, max per minute, timeout, internal-network allowlist (admin only) yes
webhooks_subscription Endpoint + event type, record filter (QQQ query filter JSON), detail level, only-these / never-these fields, API name and version yes
webhooks_delivery One row per event per endpoint: event id (the webhook-id), stored payload, status, attempts, next attempt, lease, last error, dead letter yes
webhooks_endpoint_secret Signing secrets: generated (AES-GCM encrypted) or an environment variable name; status Active, Previous (still signing during a rotation), Retired; fingerprint on the endpoint
webhooks_delivery_attempt One row per HTTP attempt: outcome, status, duration, response excerpt, error, remote address, which secrets signed it. Unique on delivery and attempt number (the claim) on the delivery

Deliveries and attempts are written only by the qbit. People act on them through the actions below.

Setup and empty states

Nothing in this qbit is seeded: endpoints, secrets and subscriptions are your choices, and deliveries and attempts are logs. Instead the app home has a Setup card (event types, encryption key, endpoints, signing secrets, subscriptions, each with its state and next step), and every table has help (help/qbit-webhooks.yaml) that explains how its rows get created and shows when the table is empty.

The event types you can subscribe to come from code, not from a table: the host's tableEventSources, processEventSources and ESB settings, custom events it fires with Webhooks.fire, and a built-in Test event, all held in the WebhooksRegistry.

In-app help

The producer registers help/qbit-webhooks.yaml, a QBIT bundle covering the app, tables, fields, actions, widgets and possible-value sources. QQQ synchronizes it on startup when the host has the helpContent table; registration is safe without that table, and inline app/table empty-state help remains available.

Hosts can register their own YAML bundle with layer: HOST through HelpBundles.register(qInstance, "help/my-host.yaml"). Use the same entry keys and roles to override selected text with deployment-specific guidance. Keep live {{label:...}}, {{values:...}} and {{default:...}} references where appropriate. If you customize the app name, supply its help under the customized app key in your host bundle.

WebhooksHelpBundleTest uses QQQ's HelpBundleAssertions against the produced instance, including the optional ESB process, to check coverage, summary length and live references. Run it with ./mvnw -q -Dtest=WebhooksHelpBundleTest test; the full ./mvnw -q verify also runs it. Keep the bundle's literal version aligned with the POM revision when releasing.

Permissions

Permission Grants
webhooks.view The app, the home, every table (read)
webhooks.manage Create and edit endpoints and subscriptions; Send Now, Replay, Cancel Delivery, Send Test Event
webhooks.admin Signing secrets (Rotate Signing Secret, environment references, the secret value), internal-network targets, deleting endpoints, Delete Deliveries

Grant WebhooksPermissions.ALL (which includes webhooks.hasAccess) to administrators. Subscribing to a table's events also needs read permission on that table. Record security locks from withSecurityFields / withRecordSecurityLocks apply to endpoints, subscriptions and deliveries. A subscription only receives records its security values could read.

Actions

All of these are table or record actions with the standard review and result screens. None is in the nav.

Action On Does
Send Now Deliveries Sends the selected unfinished deliveries now
Replay Deliveries Queues finished deliveries (dead letters included) again, with the same event id and a fresh attempt budget
Cancel Delivery Deliveries Stops pending deliveries
Delete Deliveries Deliveries Deletes finished deliveries and their attempts (admin)
Send Test Event Endpoint Sends a signed webhooks.test event now and shows the result
Rotate Signing Secret Endpoint Creates a new secret; the old one keeps signing for rotationOverlapHours (admin)

The scheduler runs delivery every deliveryIntervalSeconds (15). It runs maintenance every 5 minutes (probation, retiring old secrets) and the retention purge daily.

Configuration

Setting Default Meaning
backendName required Backend of the five tables
schedulerName none Without it nothing is sent on a schedule
tableEventSources none Local tables: <table>.inserted / updated / deleted
processEventSources none process.<name>.completed / failed
esbDestinations, esbTableEventSources, esbSkipOwnEvents none, none, true ESB topics to consume; remote tables; skip this instance's own events
maxAttempts, retryMinutes 6, [1, 5, 15, 60, 240, 720] Then the delivery is a dead letter
leaseSeconds 120 A claimed delivery whose runner died is retried after this
connectTimeoutMillis, httpTimeoutMillis, totalTimeoutMillis, maxResponseBytes 5000, 10000, 30000, 65536 HTTP limits (an endpoint's timeoutSeconds overrides the read timeout)
allowPlainHttp false https only by default
unhealthyAfterConsecutiveFailures, probationAfterMinutes, autoDisableAfterProbationFailures 10, 60, 3 Endpoint health
encryptionKeyEnvName, rotationOverlapHours WEBHOOKS_SECRET_KEY, 24 Secret storage and rotation
deliveredRetentionDays, attemptRetentionDays, deadLetterRetentionDays 400, 400, 0 0 keeps forever
snakeCaseColumnNames true Column names as in docs/ddl
appName, appLabel, appPath, tablePathPrefix webhooks, Webhooks The app and the links on its home

For receivers

Every request carries webhook-id, webhook-timestamp, webhook-signature (v1,<base64 HMAC-SHA256>, more than one during a rotation) and webhook-attempt. Verify the signature, reject old timestamps, and ignore an id you have already processed. Delivery is at least once. See docs/RECEIVERS.md for the format and code in Java, Node.js and Python.

Adopting in another app

  1. Add the dependency and run docs/ddl/webhooks-postgres.sql (as a Liquibase changeset).
  2. Produce the qbit (above) after your tables exist. Add its app under your admin app if you have one.
  3. Grant the permissions to your admins, and set WEBHOOKS_SECRET_KEY in the environment.
  4. Optional hooks, through WebhooksIntegrations:
    • publish esbTableNames() to your ESB;
    • index quickSearchTables() in Quick Search;
    • expose apiTableNames() in your QQQ API.
  5. Other qbits deliver through Webhooks.deliver(endpointName, type, data, idempotencyKey) (the channel API), and code fires domain events with Webhooks.fire(...).

Upgrading from 0.x: see docs/migration-0.x-to-1.0.md.

Build

./mvnw verify      # tests, Checkstyle and the JaCoCo gate (always use the wrapper)

Health checks

The producer registers passive checks with QQQ central health monitoring (requires QQQ 4.1.0-RC.8 or later):

Name Kind Category Interval
webhooks.endpoint.<id> DEPENDENCY INTEGRATION 300 seconds
webhooks.dispatcher DEPENDENCY INTEGRATION 300 seconds

Both belong to component webhooks. Endpoint checks read the existing state machine: HEALTHY means UP; UNHEALTHY or PROBATION means DEGRADED; automatically disabled means DOWN. Active and automatically disabled endpoints are discovered dynamically; paused and manually disabled endpoints are excluded. Details contain only healthStatus, consecutiveFailures and unhealthySince when present.

The dispatcher reports oldestPendingSeconds, measured from the oldest queued delivery's createDate across PENDING, RETRY_WAIT and SENDING. More than 15 minutes is DEGRADED; more than 60 minutes is DOWN. An empty queue reports UP with zero age. Checks do not send requests or change endpoint state.

The home shows one native central status line linking to the webhooks checks in System Health. Disable checks centrally with health_check.enabled. The endpoint state machine, alerts and Send Test Event action remain unchanged.

License

Apache-2.0. See LICENSE and NOTICE.

About

QBit for webhook handling. Inbound and outbound webhook management.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages