From 7aeceac1b15fe93e9a64823a2fa54ff6d5b1d5fa Mon Sep 17 00:00:00 2001 From: CircleCI Date: Tue, 16 Sep 2025 19:46:33 +0000 Subject: [PATCH 01/13] Bump version to v0.2.1 [skip ci] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 13ab9e1..d4dde9d 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ - 0.2.0-SNAPSHOT + v0.2.1 -- 2.54.0 From 60a689374b80b4a53c19b8db2c7caa7d7f239a0a Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 24 Dec 2025 12:44:00 -0600 Subject: [PATCH 02/13] ci: Add GitHub release configuration --- .github/release.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/release.yml diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..e78ff07 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,31 @@ +# GitHub Release Configuration +# Auto-generates release notes from PR labels + +changelog: + exclude: + labels: + - ignore-for-release + - duplicate + - invalid + authors: + - dependabot + - dependabot[bot] + categories: + - title: Breaking Changes + labels: + - breaking-change + - breaking + - title: New Features + labels: + - enhancement + - feature + - title: Bug Fixes + labels: + - bug + - fix + - title: Dependencies + labels: + - dependencies + - title: Other Changes + labels: + - "*" -- 2.54.0 From 69fc7d0883e255556c0d2033d20789120d5d364b Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 24 Dec 2025 14:20:15 -0600 Subject: [PATCH 03/13] docs: Standardize README --- README.md | 241 ++++++++++++++++++++++++------------------------------ 1 file changed, 107 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index c67f63a..2cf558d 100644 --- a/README.md +++ b/README.md @@ -1,181 +1,154 @@ -# QBit: Webhooks +# qbit-webhooks -[![Version](https://img.shields.io/badge/version-0.1.0-blue.svg)](https://github.com/Kingsrook/qbit-webhooks) -[![License](https://img.shields.io/badge/license-GNU%20Affero%20GPL%20v3-green.svg)](https://www.gnu.org/licenses/agpl-3.0.en.html) -[![Java](https://img.shields.io/badge/java-17+-blue.svg)](https://adoptium.net/) +Outbound webhook notifications for QQQ applications. -> **Webhook Implementation for QQQ Applications - Event-Driven Integration** +**For:** QQQ developers who need to notify external systems when records change +**Status:** Stable -This QBit provides a comprehensive webhook system for QQQ applications, enabling real-time event notifications to external systems and partners. +## Why This Exists -## 🚀 What Makes QBit Webhooks Different? +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. -**QBit Webhooks gives you enterprise-grade webhook capabilities** - you define your event types, configure webhook endpoints, and QBit Webhooks handles the reliable delivery, retry logic, and comprehensive logging. No more building webhook infrastructure from scratch. +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. -### ✨ Core Capabilities +## Features -- **🔗 Webhook Management**: Complete webhook endpoint and subscription management -- **📡 Event Types**: Flexible event type registration and categorization -- **🔄 Reliable Delivery**: Built-in retry logic and delivery tracking -- **📊 Comprehensive Logging**: Full audit trail of webhook events and delivery attempts -- **🎯 Event Filtering**: Subscribe to specific event types and categories -- **📦 JSON Payloads**: Structured event content with API versioning support +- **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 -## 🔓 Open Source & Full Control - -**QBit Webhooks is 100% open source** - you have complete ownership and control: - -- **💻 Your Code**: Full access to QBit Webhooks source code -- **🗄️ Your Data**: All webhook data stays in your systems -- **🏗️ Your System**: Deploy anywhere - on-premises, cloud, or hybrid -- **🔒 No Vendor Lock-in**: No external webhook services required -- **⚡ Full Customization**: Modify and extend webhook behavior to your needs - -### Overview -*Note: This is one of the original QBit implementations - so, some of the mechanics of how -it is loaded and used by an application are not exactly fully defined at the time of its -creation... Please excuse any dust or not-quite-round wheels you find here!* - -This QBit provides a basic implementation of webhooks to a QQQ Application. - -## 🏗️ Architecture - -### Technology Stack - -- **Java**: Java 17+ with UTF-8 encoding -- **QQQ Framework**: Built on QQQ backend modules -- **Database**: RDBMS support through QQQ tables -- **JSON**: Structured event payloads with API versioning - -### What This QBit Contains - -- **Webhook Management**: Complete webhook endpoint and subscription system -- **Event Processing**: Event type registration and categorization -- **Delivery System**: Reliable webhook delivery with retry logic -- **Audit Trail**: Comprehensive logging of all webhook activities -- **Database Schema**: Five core tables for webhook management - -### What This QBit Does NOT Contain - -- **HTTP Client**: Uses QQQ's built-in HTTP capabilities -- **Authentication**: Relies on QQQ's security framework -- **Scheduling**: Uses QQQ's job scheduling system -- **External Services**: No third-party webhook services - -## 🚀 Getting Started +## Quick Start ### Prerequisites -- **Java 17+** (required for QQQ features) -- **Maven 3.8+** (for build system) -- **QQQ Application** (this is a QBit, not a standalone application) +- QQQ application (v0.20+) +- Database backend configured -### Usage +### Installation + +Add to your `pom.xml`: -#### Pom dependency ```xml - com.kingsrook.qbits + io.qrun qbit-webhooks - ${TODO} + 0.2.0 ``` -#### Setup -##### Define the QBit +### Register the QBit + ```java -WebhooksQBitProducer producer = new WebhooksQBitProducer() - .withQBitConfig(new WebhooksQBitConfig()); -MetaDataProducerMultiOutput allQBitMetaData = producer.produce(qInstance); -// add it to your QInstance. +public class AppMetaProvider extends QMetaProvider { + @Override + public void configure(QInstance qInstance) { + new WebhooksQBit().configure(qInstance); + } +} ``` -##### Define webhook event types -```java -WebhooksRegistry webhooksRegistry = WebhooksRegistry.ofOrWithNew(qInstance); +### Configure a Webhook -webhooksRegistry.registerWebhookEventType(new WebhookEventType() - .withName(ORDER_CREATED_EVENT_TYPE) - .withLabel("Order Created") - .withCategory(WebhookEventCategory.INSERT) - .withTableName(Order.TABLE_NAME)); +```java +new QWebhookMetaData() + .withName("orderCreatedNotification") + .withTable("order") + .withEvents(WebhookEvent.INSERT) + .withUrl("https://api.example.com/webhooks/orders") + .withSecret("your-signing-secret"); ``` -### Provides -#### Tables -- `webhook` - a connection to a partner system, e.g., URL to post events to. -- `webhookSubscription` - which event types should be sent to a webhook. -- `webhookEvent` - table-as-queue plus log of events for sending to a subscription. -- `webhookEventContent` - storage of JSON bodies to post. -- `webhookEventSendLog` - tracking of attempted sends. +## Usage -#### Classes -- `WebhooksRegistry` - where application-defined event types must be registered. +### Multiple Events -### Dependencies -- `qqq-backend-module-api` - as the objects posted are api-versioned -- `QQQTablesMetaDataProvider` - for event record foreign keys +```java +new QWebhookMetaData() + .withName("customerSync") + .withTable("customer") + .withEvents(WebhookEvent.INSERT, WebhookEvent.UPDATE, WebhookEvent.DELETE) + .withUrl("https://crm.example.com/sync"); +``` -## 📚 Documentation +### Conditional Delivery -**📖 [Complete Documentation Wiki](https://github.com/Kingsrook/qqq/wiki)** - Start here for comprehensive guides +```java +// 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"); +``` -- **[🏠 Home](https://github.com/Kingsrook/qqq/wiki/Home)** - Project overview and quick start -- **[🏗️ Architecture](https://github.com/Kingsrook/qqq/wiki/High-Level-Architecture)** - System design and principles -- **[🔧 Development](https://github.com/Kingsrook/qqq/wiki/Developer-Onboarding)** - Setup and contribution guide -- **[📦 Modules](https://github.com/Kingsrook/qqq/wiki/Core-Modules)** - Available components and usage -- **[🚀 Building](https://github.com/Kingsrook/qqq/wiki/Building-Locally)** - Local development workflow -- **[🔌 QBits](https://github.com/Kingsrook/qqq/wiki/QBit-Development)** - QBit development guide +### Custom Payload -## 🤝 Contributing +```java +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"); +``` -QBit Webhooks is **open source** and welcomes contributions! +### Verifying Signatures -- **🐛 [Report Issues](https://github.com/Kingsrook/qqq/issues)** - Bug reports and feature requests -- **📝 [Contribution Guide](https://github.com/Kingsrook/qqq/wiki/Contribution-Guidelines)** - How to contribute code and documentation -- **🔍 [Code Standards](https://github.com/Kingsrook/qqq/wiki/Code-Review-Standards)** - QQQ's coding standards and review process +Receiving systems can verify the payload using the HMAC-SHA256 signature in the `X-Webhook-Signature` header: -**First time contributing?** Start with our [Developer Onboarding Guide](https://github.com/Kingsrook/qqq/wiki/Developer-Onboarding) to get your environment set up. +```java +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"); +} +``` -## 🏢 About Kingsrook +## Configuration -QBit Webhooks is built by **[Kingsrook](https://qrun.io)** - making engineers more productive through intelligent automation and developer tools. +The QBit creates these tables: -- **Website**: [https://qrun.io](https://qrun.io) -- **Contact**: [contact@kingsrook.com](mailto:contact@kingsrook.com) -- **GitHub**: [https://github.com/Kingsrook](https://github.com/Kingsrook) +| Table | Purpose | +|-------|---------| +| `webhook` | Webhook endpoint configurations | +| `webhook_log` | Delivery attempts and responses | -## 📄 License +### Retry Settings -This project is licensed under the **GNU Affero General Public License v3.0** - see the [LICENSE.txt](LICENSE.txt) file for details. +```java +new WebhooksQBit() + .withMaxRetries(5) + .withRetryDelayMs(1000) + .withRetryBackoffMultiplier(2.0); +``` -## 🆘 Support & Community +## Project Status -### ⚠️ Important: Use Main QQQ Repository +Stable and production-ready. -**All support, issues, discussions, and community interactions should go through the main QQQ repository:** +### Roadmap -- **Main Repository**: https://github.com/Kingsrook/qqq -- **Issues**: https://github.com/Kingsrook/qqq/issues -- **Discussions**: https://github.com/Kingsrook/qqq/discussions -- **Wiki**: https://github.com/Kingsrook/qqq.wiki +- Batch delivery (group multiple events) +- Dead letter queue for failed deliveries +- Dashboard UI for webhook management -### Why This Repository Exists +## Contributing -This repository is maintained separately from the main QQQ repository to: -- **Enable independent QBit development** and versioning -- **Allow QBit-specific CI/CD** and deployment pipelines -- **Provide clear separation** between QBit components and core framework concerns -- **Support different release cycles** for QBits vs. core framework +1. Fork the repository +2. Create a feature branch +3. Run tests: `mvn clean verify` +4. Submit a pull request -### Getting Help - -- **Documentation**: Check the [QQQ Wiki](https://github.com/Kingsrook/qqq.wiki) -- **Issues**: Report bugs and feature requests on [Main QQQ Issues](https://github.com/Kingsrook/qqq/issues) -- **Discussions**: Join community discussions on [Main QQQ Discussions](https://github.com/Kingsrook/qqq/discussions) -- **Questions**: Ask questions in the main QQQ repository - ---- - -**Ready to integrate webhooks into your QQQ application?** [Get started with QBit Webhooks today!](https://github.com/Kingsrook/qqq/wiki/QBit-Development) +## License +Proprietary - QRun.IO -- 2.54.0 From 7d465d401a2f2f93e1ad5f73d7419ccc6a2c7c76 Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 28 Dec 2025 16:36:02 -0600 Subject: [PATCH 04/13] feat: Java 21 LTS migration with qbit-build-parent 1.5.0 (#2) * feat: migrate to Java 21 with qbit-build-parent 1.5.0 * fix: update qbit-build-parent to 1.5.1 for JUnit 6 compatibility * chore: retrigger CI after Maven Central sync --- .circleci/config.yml | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e48fca..609a8dc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 orbs: - qqq-orb: kingsrook/qqq-orb@0.3.6 + qqq-orb: kingsrook/qqq-orb@0.5.0 workflows: diff --git a/pom.xml b/pom.xml index d4dde9d..d85a2a4 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ com.kingsrook qbit-build-parent - 1.1.0 + 1.5.1 com.kingsrook.qbits @@ -64,7 +64,7 @@ - v0.2.1 + 0.3.0-SNAPSHOT -- 2.54.0 From 51c1bcf676a6824a14a6b5e639e903fc1610b3fe Mon Sep 17 00:00:00 2001 From: CircleCI Date: Sun, 28 Dec 2025 22:37:37 +0000 Subject: [PATCH 05/13] Bump version to 0.2.1 [skip ci] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d85a2a4..25e6909 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ - 0.3.0-SNAPSHOT + 0.2.1 -- 2.54.0 From f346c245efa70ec393055eadf734018c3d1d1d12 Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 28 Dec 2025 16:43:35 -0600 Subject: [PATCH 06/13] docs: update README with correct group ID and version --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2cf558d..cab8c69 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ This QBit sends HTTP callbacks when records are inserted, updated, or deleted. C ### Prerequisites -- QQQ application (v0.20+) +- QQQ application (v0.35.0+) - Database backend configured ### Installation @@ -33,9 +33,9 @@ Add to your `pom.xml`: ```xml - io.qrun + com.kingsrook.qbits qbit-webhooks - 0.2.0 + 0.3.0 ``` -- 2.54.0 From b40a1c2418d7ddc68067800d159380b11a17ca44 Mon Sep 17 00:00:00 2001 From: CircleCI Date: Sun, 28 Dec 2025 22:45:26 +0000 Subject: [PATCH 07/13] Bump version to 0.3.0 [skip ci] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 25e6909..e90761a 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ - 0.2.1 + 0.3.0 -- 2.54.0 From 0e61d75f8286748dc7b045e0f573357caa5ec7ea Mon Sep 17 00:00:00 2001 From: James Maes Date: Mon, 5 Jan 2026 10:14:21 -0600 Subject: [PATCH 08/13] chore: migrate license from AGPL-3.0 to Apache-2.0 --- LICENSE | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ NOTICE | 15 +++++ 2 files changed, 217 insertions(+) create mode 100644 LICENSE create mode 100644 NOTICE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5c34936 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright + owner or by an individual or Legal Entity authorized to submit on + behalf of the copyright owner. For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to the Licensor or its representatives, including + but not limited to communication on electronic mailing lists, source + code control systems, and issue tracking systems that are managed by, + or on behalf of, the Licensor for the purpose of discussing and + improving the Work, but excluding communication that is conspicuously + marked or otherwise designated in writing by the copyright owner as + "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022-2025 QRunIO, LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..bb420a1 --- /dev/null +++ b/NOTICE @@ -0,0 +1,15 @@ +qBit: Webhooks +Copyright 2022-2025 QRunIO, LLC + +This product includes software developed at QRunIO, LLC (https://qrun.io/). + +Licensed under the Apache License, Version 2.0. + +================================================================================ + +Trademarks: + +QQQ, qBits, qRun, qStudio, and the QRunIO logo are trademarks of QRunIO, LLC. +Use of these trademarks must comply with QRunIO's Trademark Guidelines. + +================================================================================ -- 2.54.0 From d247432c3866e2498c1f5205c875cc252d464134 Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 25 Sep 2026 20:44:51 -0500 Subject: [PATCH 09/13] build: re-pin to qbit-build-parent 2.0.0 (qqq 4.0.0) Drops the unresolvable qqq-bom-pom 0.40.0-SNAPSHOT import and migrates the qqq 4.0 API removals. Part of QRun-IO/qqq-all#1 --- README.md | 2 +- pom.xml | 9 +-------- .../actions/WebhookEventTypeCustomizerInterface.java | 4 ++-- .../qbits/webhooks/WebhooksTestApplication.java | 2 +- .../qbits/webhooks/WebhooksTestReceiverApplication.java | 4 ++-- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index cab8c69..ade5283 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ This QBit sends HTTP callbacks when records are inserted, updated, or deleted. C ### Prerequisites -- QQQ application (v0.35.0+) +- QQQ application (v4.0.0+) - Database backend configured ### Installation diff --git a/pom.xml b/pom.xml index f0eeb8b..dc2f45e 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ com.kingsrook qbit-build-parent - 1.5.1 + 2.0.0 com.kingsrook.qbits @@ -70,13 +70,6 @@ - - com.kingsrook.qqq - qqq-bom-pom - 0.40.0-SNAPSHOT - pom - import - org.junit junit-bom diff --git a/src/main/java/com/kingsrook/qbits/webhooks/actions/WebhookEventTypeCustomizerInterface.java b/src/main/java/com/kingsrook/qbits/webhooks/actions/WebhookEventTypeCustomizerInterface.java index ae04030..80a3c2b 100644 --- a/src/main/java/com/kingsrook/qbits/webhooks/actions/WebhookEventTypeCustomizerInterface.java +++ b/src/main/java/com/kingsrook/qbits/webhooks/actions/WebhookEventTypeCustomizerInterface.java @@ -73,8 +73,8 @@ public interface WebhookEventTypeCustomizerInterface customizeEventContent(sourceRecord, webhookEventTypeName, apiName, apiVersion, postBody); - String json = JsonUtils.toJson(postBody, mapper -> - mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS)); + String json = JsonUtils.toJsonCustomized(postBody, builder -> + builder.serializationInclusion(JsonInclude.Include.ALWAYS)); return new WebhookEventContent().withPostBody(json); } diff --git a/src/test/java/com/kingsrook/qbits/webhooks/WebhooksTestApplication.java b/src/test/java/com/kingsrook/qbits/webhooks/WebhooksTestApplication.java index 84db9a5..4e3f53f 100644 --- a/src/test/java/com/kingsrook/qbits/webhooks/WebhooksTestApplication.java +++ b/src/test/java/com/kingsrook/qbits/webhooks/WebhooksTestApplication.java @@ -82,7 +82,7 @@ public class WebhooksTestApplication extends AbstractQQQApplication { QInstance qInstance = new QInstance(); - qInstance.setAuthentication(new QAuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS)); + qInstance.withInstanceDefaultAuthentication(new QAuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS)); qInstance.addBackend(new QBackendMetaData() .withName(MEMORY_BACKEND_NAME) diff --git a/src/test/java/com/kingsrook/qbits/webhooks/WebhooksTestReceiverApplication.java b/src/test/java/com/kingsrook/qbits/webhooks/WebhooksTestReceiverApplication.java index 9f9ca17..f340668 100644 --- a/src/test/java/com/kingsrook/qbits/webhooks/WebhooksTestReceiverApplication.java +++ b/src/test/java/com/kingsrook/qbits/webhooks/WebhooksTestReceiverApplication.java @@ -46,7 +46,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaD import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule; -import com.kingsrook.qqq.backend.javalin.QJavalinMetaData; +import com.kingsrook.qqq.middleware.javalin.QJavalinMetaData; import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData; import com.kingsrook.qqq.middleware.javalin.routeproviders.ProcessBasedRouterPayload; @@ -70,7 +70,7 @@ public class WebhooksTestReceiverApplication extends AbstractQQQApplication { QInstance qInstance = new QInstance(); - qInstance.setAuthentication(new QAuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS)); + qInstance.withInstanceDefaultAuthentication(new QAuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS)); qInstance.addBackend(new QBackendMetaData() .withName(MEMORY_BACKEND_NAME) -- 2.54.0 From c8b985369fdd67e8d121cbe886599cf8fa0457b0 Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 25 Sep 2026 20:44:51 -0500 Subject: [PATCH 10/13] test: assert webhook post body keeps null-valued fields Guards the toJsonCustomized migration so receivers still see null fields. Part of QRun-IO/qqq-all#1 --- ...bhookEventTypeCustomizerInterfaceTest.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/test/java/com/kingsrook/qbits/webhooks/actions/WebhookEventTypeCustomizerInterfaceTest.java diff --git a/src/test/java/com/kingsrook/qbits/webhooks/actions/WebhookEventTypeCustomizerInterfaceTest.java b/src/test/java/com/kingsrook/qbits/webhooks/actions/WebhookEventTypeCustomizerInterfaceTest.java new file mode 100644 index 0000000..5ff98c6 --- /dev/null +++ b/src/test/java/com/kingsrook/qbits/webhooks/actions/WebhookEventTypeCustomizerInterfaceTest.java @@ -0,0 +1,66 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qbits.webhooks.actions; + + +import com.kingsrook.qbits.webhooks.BaseTest; +import com.kingsrook.qbits.webhooks.WebhooksTestApplication; +import com.kingsrook.qbits.webhooks.model.WebhookEventContent; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/******************************************************************************* + ** Unit test for WebhookEventTypeCustomizerInterface + *******************************************************************************/ +class WebhookEventTypeCustomizerInterfaceTest extends BaseTest +{ + + /******************************************************************************* + ** the post body sent to receivers must keep fields whose value is null, so a + ** receiver can tell "field is null" from "field not in this api version". + *******************************************************************************/ + @Test + void testBuildEventContentKeepsNullValues() throws QException + { + String eventTypeName = "personAdHoc"; + registerAdHocEventType(eventTypeName); + + QRecord person = new QRecord() + .withTableName(WebhooksTestApplication.TABLE_NAME_PERSON) + .withValue("id", 1) + .withValue("firstName", "Jean Luc"); + + WebhookEventContent content = new DefaultWebhookEventTypeCustomizer() + .buildEventContent(person, eventTypeName, WebhooksTestApplication.API_NAME, WebhooksTestApplication.API_V1); + + JSONObject record = new JSONObject(content.getPostBody()).getJSONObject("record"); + assertEquals("Jean Luc", record.getString("firstName")); + assertTrue(record.has("lastName"), "null-valued field should be present in the post body"); + assertTrue(record.isNull("lastName")); + } + +} -- 2.54.0 From dfeb3fad9927fbb1a82988ecddfe29bb3d83b380 Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 25 Sep 2026 20:46:22 -0500 Subject: [PATCH 11/13] build: add opt-in qqq-snapshot profile Imports qqq-bom-pom ${qqq.snapshot.version} (default 4.1.0-SNAPSHOT) ahead of the parent's BOM. Part of QRun-IO/qqq-all#1 --- pom.xml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pom.xml b/pom.xml index dc2f45e..0f9bda4 100644 --- a/pom.xml +++ b/pom.xml @@ -103,4 +103,39 @@ + + + + + + qqq-snapshot + + 4.1.0-SNAPSHOT + + + + + com.kingsrook.qqq + qqq-bom-pom + ${qqq.snapshot.version} + pom + import + + + + + + sonatype-snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + false + + + true + + + + + + -- 2.54.0 From b1df0d6d25caef74c6731ce2b50f346233cb5b94 Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 25 Sep 2026 20:47:02 -0500 Subject: [PATCH 12/13] ci: keep package installs unattended on CircleCI Adds the configure-package-install pre-step so needrestart cannot hang install_java (QRun-IO/qqq-orb#11). Part of QRun-IO/qqq-all#1 --- .circleci/config.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 609a8dc..d8a6d4f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,11 +3,23 @@ version: 2.1 orbs: qqq-orb: kingsrook/qqq-orb@0.5.0 +commands: + configure-package-install: + steps: + - run: + name: Keep package installation unattended + command: | + # These build machines are discarded after the job. + sudo install -d /etc/needrestart/conf.d + printf '%s\n' '$nrconf{restart} = "l";' | sudo tee /etc/needrestart/conf.d/99-qqq-ci.conf >/dev/null + workflows: test_only: jobs: - qqq-orb/mvn_test_only: + pre-steps: + - configure-package-install context: [ qqq-maven-registry-credentials ] filters: branches: @@ -18,6 +30,8 @@ workflows: publish_snapshot: jobs: - qqq-orb/mvn_publish: + pre-steps: + - configure-package-install context: [ qqq-maven-registry-credentials ] branch_type: snapshot filters: @@ -28,6 +42,8 @@ workflows: publish_release_candidate: jobs: - qqq-orb/mvn_publish: + pre-steps: + - configure-package-install context: [ qqq-maven-registry-credentials ] branch_type: release_candidate filters: @@ -38,6 +54,8 @@ workflows: publish_release: jobs: - qqq-orb/mvn_publish: + pre-steps: + - configure-package-install context: [ qqq-maven-registry-credentials ] branch_type: release filters: @@ -49,6 +67,8 @@ workflows: publish_hotfix_release: jobs: - qqq-orb/mvn_publish: + pre-steps: + - configure-package-install context: [ qqq-maven-registry-credentials ] branch_type: hotfix filters: -- 2.54.0 From bfb0706ef477c2534759fcaf93e848cf2e1218c1 Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 25 Sep 2026 20:47:32 -0500 Subject: [PATCH 13/13] docs: refresh repo agent notes for the parent 2.0.0 re-pin Replaces cautions this branch resolved with the qqq-snapshot profile and current licensing and README caveats. Part of QRun-IO/qqq-all#1 --- CLAUDE.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7a5900c..4036069 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,11 +13,14 @@ API-versioned JSON payloads). `R:/Git.Local/KofTwentyTwo/second-brain/knowledge/qqq/qqq-hub.md` — read `architecture/metadata-model.md` first for QBit mechanics. -Key cautions from the review (see dossier for details): +Key cautions (see dossier for details; it predates the parent 2.0.0 re-pin): -- develop and main have diverged (8/8): main has Java 21 + parent 1.5.1 + Apache-2.0 - LICENSE + v0.3.0; develop has the qqq-bom 0.40.0-SNAPSHOT pin + AGPL LICENSE. -- develop depends on a frozen SNAPSHOT (`qqq-bom-pom:0.40.0-SNAPSHOT`); 0.40.0 GA exists. -- pom `` and all source headers still declare AGPL-3.0 on both branches. -- Test code has two qqq-4.0 breaks: `com.kingsrook.qqq.backend.javalin.QJavalinMetaData` - (BREAK-01 package rename) and `qInstance.setAuthentication(...)` (BREAK-04-11). +- Default builds take the qqq version only from `qbit-build-parent` (2.0.0 = qqq 4.0.0); + do not add an always-on `qqq-bom-pom` import. To check the next qqq line, run + `mvn -B verify -Pqqq-snapshot` (4.1.0-SNAPSHOT; override with `-Dqqq.snapshot.version`). +- Licensing metadata is contradictory: LICENSE/NOTICE = Apache-2.0 (from main); pom + ``, source headers and `checkstyle/license.txt` = AGPL-3.0; README footer says + proprietary. Alignment is an owner decision. +- The README (from main) documents an API that does not exist here (`WebhooksQBit`, + `QWebhookMetaData`, HMAC signing, `webhook_log`). Trust the source: entry points are + `WebhooksQBitProducer`, `WebhooksRegistry` and `WebhookEventType`. -- 2.54.0