forge-recovery 64087ba10e
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
Shared Java Maven CI with Docker / secrets (push) Successful in 20s
QRun-IO/ci-policy: native / native-result (push) Successful in 6s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (push) Successful in 18s
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (push) Successful in 1m14s
Shared Java Maven CI with Docker / verify (push) Successful in 1m32s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (push) Successful in 13s
QRun-IO/ci-policy: ci / java-docker-3 (push) Successful in 32m16s
Shared Java Maven CI with Docker / gate (push) Successful in 21s
qrun-release / release (push) Successful in 32m59s
QRun-IO/ci-policy: ci / ci-result (push) Successful in 3s
Shared QRun source version / version (push) Failing after 10m18s
qrun-release / postpublish (push) Failing after 10m18s
ci: enroll shared Gitea lifecycle
2026-11-02 22:25:21 +00:00
2025-06-13 20:35:04 -05:00
2025-06-13 20:35:04 -05:00
2025-12-28 22:45:26 +00:00

qbit-webhooks

Outbound webhook notifications for QQQ applications.

For: QQQ developers who need to notify external systems when records change
Status: Stable

Why This Exists

Modern applications don't exist in isolation. When an order is created, you might need to notify a fulfillment system. When a user updates their profile, a CRM needs to know. Polling for changes is inefficient and adds latency.

This QBit sends HTTP callbacks when records are inserted, updated, or deleted. Configure endpoints, select which events to send, and the system handles delivery with retries and logging.

Features

  • Event-Driven Delivery - Fires on insert, update, delete operations
  • Configurable Endpoints - Register multiple webhook URLs per table
  • Payload Customization - Include full record, changed fields only, or custom format
  • Retry Logic - Automatic retries with exponential backoff
  • Delivery Logging - Track success, failures, and response codes
  • Secret Signing - HMAC signatures for payload verification

Quick Start

Prerequisites

  • QQQ application (v0.35.0+)
  • Database backend configured

Installation

Add to your pom.xml:

<dependency>
    <groupId>com.kingsrook.qbits</groupId>
    <artifactId>qbit-webhooks</artifactId>
    <version>0.3.0</version>
</dependency>

Register the QBit

public class AppMetaProvider extends QMetaProvider {
    @Override
    public void configure(QInstance qInstance) {
        new WebhooksQBit().configure(qInstance);
    }
}

Configure a Webhook

new QWebhookMetaData()
    .withName("orderCreatedNotification")
    .withTable("order")
    .withEvents(WebhookEvent.INSERT)
    .withUrl("https://api.example.com/webhooks/orders")
    .withSecret("your-signing-secret");

Usage

Multiple Events

new QWebhookMetaData()
    .withName("customerSync")
    .withTable("customer")
    .withEvents(WebhookEvent.INSERT, WebhookEvent.UPDATE, WebhookEvent.DELETE)
    .withUrl("https://crm.example.com/sync");

Conditional Delivery

// Only fire when status changes to 'approved'
new QWebhookMetaData()
    .withName("approvalNotification")
    .withTable("order")
    .withEvents(WebhookEvent.UPDATE)
    .withCondition((oldRecord, newRecord) -> 
        "approved".equals(newRecord.getValue("status")) &&
        !"approved".equals(oldRecord.getValue("status")))
    .withUrl("https://notify.example.com/approved");

Custom Payload

new QWebhookMetaData()
    .withName("slackNotification")
    .withTable("alert")
    .withEvents(WebhookEvent.INSERT)
    .withPayloadTransformer((record) -> Map.of(
        "text", "New alert: " + record.getValue("message"),
        "channel", "#alerts"
    ))
    .withUrl("https://hooks.slack.com/services/xxx");

Verifying Signatures

Receiving systems can verify the payload using the HMAC-SHA256 signature in the X-Webhook-Signature header:

String payload = requestBody;
String signature = request.getHeader("X-Webhook-Signature");
String expected = HmacUtils.hmacSha256Hex(secret, payload);
if (!expected.equals(signature)) {
    throw new SecurityException("Invalid signature");
}

Configuration

The QBit creates these tables:

Table Purpose
webhook Webhook endpoint configurations
webhook_log Delivery attempts and responses

Retry Settings

new WebhooksQBit()
    .withMaxRetries(5)
    .withRetryDelayMs(1000)
    .withRetryBackoffMultiplier(2.0);

Project Status

Stable and production-ready.

Roadmap

  • Batch delivery (group multiple events)
  • Dead letter queue for failed deliveries
  • Dashboard UI for webhook management

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Run tests: mvn clean verify
  4. Submit a pull request

License

Proprietary - QRun.IO

S
Description
QBit for webhook handling. Inbound and outbound webhook management.
Readme
2.4 MiB
0 Stars 3 Watchers 0 Forks
2025-12-28 22:44:42 +00:00
Languages
Java 100%