* 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
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.20+)
- Database backend configured
Installation
Add to your pom.xml:
<dependency>
<groupId>io.qrun</groupId>
<artifactId>qbit-webhooks</artifactId>
<version>0.2.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
- Fork the repository
- Create a feature branch
- Run tests:
mvn clean verify - Submit a pull request
License
Proprietary - QRun.IO