Skip to content

Webhooks

Webhooks enable real-time notifications when events occur in your artifact registry, allowing integration with CI/CD pipelines, monitoring systems, and custom workflows.

Webhook Events

Artifact Keeper emits webhooks for these event types:

artifact.uploaded

Triggered when a new artifact is successfully uploaded.

Payload example:

{
"event": "artifact.uploaded",
"timestamp": "2026-02-01T12:34:56Z",
"data": {
"artifact_id": "artifact-123",
"repository_id": "repo-456",
"repository_name": "my-maven-repo",
"package_name": "com.example:my-app",
"version": "1.2.3",
"format": "maven",
"size_bytes": 15728640,
"checksum_sha256": "a3b5c8...",
"uploader": {
"user_id": "user-789",
"username": "alice"
}
}
}

artifact.downloaded

Triggered when an artifact is downloaded.

Payload example:

{
"event": "artifact.downloaded",
"timestamp": "2026-02-01T12:35:00Z",
"data": {
"artifact_id": "artifact-123",
"repository_name": "my-maven-repo",
"package_name": "com.example:my-app",
"version": "1.2.3",
"downloader": {
"user_id": "user-890",
"username": "bob",
"ip_address": "203.0.113.42"
}
}
}

artifact.deleted

Triggered when an artifact is deleted.

Payload example:

{
"event": "artifact.deleted",
"timestamp": "2026-02-01T12:36:00Z",
"data": {
"artifact_id": "artifact-123",
"repository_name": "my-maven-repo",
"package_name": "com.example:my-app",
"version": "1.2.3",
"deleted_by": {
"user_id": "user-789",
"username": "alice"
}
}
}

scan.completed

Triggered when a security scan completes.

Payload example:

{
"event": "scan.completed",
"timestamp": "2026-02-01T12:37:00Z",
"data": {
"scan_id": "scan-456",
"artifact_id": "artifact-123",
"package_name": "com.example:my-app",
"version": "1.2.3",
"scanner": "trivy",
"status": "completed",
"vulnerabilities": {
"critical": 0,
"high": 2,
"medium": 5,
"low": 12
},
"report_url": "https://registry.example.com/scans/scan-456"
}
}

repository.created

Triggered when a new repository is created.

repository.deleted

Triggered when a repository is deleted.

user.created

Triggered when a new user is created.

policy.violated

Triggered when a security policy is violated (e.g., vulnerable artifact upload blocked).

Creating Webhooks

Via API

Terminal window
curl -X POST https://registry.example.com/api/v1/webhooks \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-service.example.com/webhook",
"events": ["artifact.uploaded", "scan.completed"],
"description": "Notify CI/CD pipeline",
"secret": "your-webhook-secret",
"active": true,
"filters": {
"repository_id": "repo-456"
}
}'

Response:

{
"id": "webhook-789",
"url": "https://your-service.example.com/webhook",
"events": ["artifact.uploaded", "scan.completed"],
"secret": "your-webhook-secret",
"active": true,
"created_at": "2026-02-01T12:00:00Z"
}

Via Web UI

  1. Navigate to Settings > Webhooks
  2. Click “Create Webhook”
  3. Enter webhook URL and select events
  4. Configure filters (optional)
  5. Save webhook

Webhook Configuration

URL

The endpoint that will receive POST requests with event payloads.

Requirements:

  • Must be HTTPS in production
  • Must return 2xx status code within 30 seconds
  • Should be idempotent (may receive duplicate events)

Events

Select which events trigger this webhook:

{
"events": [
"artifact.uploaded",
"artifact.downloaded",
"artifact.deleted",
"scan.completed",
"repository.created",
"policy.violated"
]
}

Use "events": ["*"] to subscribe to all events.

Secret

Optional shared secret for verifying webhook authenticity:

{
"secret": "your-webhook-secret"
}

Artifact Keeper includes an HMAC-SHA256 signature in the X-Webhook-Signature header:

X-Webhook-Signature: sha256=5d7e8f9a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e

Verify in your webhook handler:

import hmac
import hashlib
def verify_signature(payload, signature, secret):
expected = 'sha256=' + hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)

Filters

Limit webhooks to specific conditions:

{
"filters": {
"repository_id": "repo-456",
"format": "docker",
"severity": ["critical", "high"]
}
}

Available filters:

  • repository_id: Specific repository
  • format: Package format (docker, maven, npm, etc.)
  • severity: For scan.completed events
  • user_id: Specific user actions

Webhook Delivery

Request Format

Webhooks are sent as HTTP POST requests:

POST /webhook HTTP/1.1
Host: your-service.example.com
Content-Type: application/json
X-Webhook-ID: webhook-789
X-Webhook-Event: artifact.uploaded
X-Webhook-Signature: sha256=5d7e8f9a...
X-Webhook-Delivery: delivery-123
{
"event": "artifact.uploaded",
"timestamp": "2026-02-01T12:34:56Z",
"data": { ... }
}

Headers

  • Content-Type: Always application/json
  • X-Webhook-ID: Webhook configuration ID
  • X-Webhook-Event: Event type
  • X-Webhook-Signature: HMAC signature (if secret configured)
  • X-Webhook-Delivery: Unique delivery attempt ID
  • User-Agent: Artifact-Keeper-Webhook/1.0

Response Handling

Your webhook endpoint should:

  • Return 2xx status code for success
  • Return quickly (< 30 seconds)
  • Handle duplicate deliveries idempotently

Example handler:

app.post('/webhook', express.json(), (req, res) => {
// Verify signature
const signature = req.headers['x-webhook-signature'];
if (!verifySignature(req.body, signature, SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process event
const { event, data } = req.body;
console.log(`Received ${event}:`, data);
// Acknowledge receipt immediately
res.status(200).send('OK');
// Process asynchronously
processEvent(event, data);
});

Retry Logic

If delivery fails (non-2xx response or timeout):

  1. Retry after 1 minute
  2. Retry after 5 minutes
  3. Retry after 30 minutes
  4. Retry after 2 hours
  5. Retry after 6 hours
  6. Give up after 24 hours

Configure retry behavior:

Terminal window
WEBHOOK_MAX_RETRIES=5
WEBHOOK_RETRY_BACKOFF=exponential # or 'linear'

Managing Webhooks

List Webhooks

Terminal window
curl https://registry.example.com/api/v1/webhooks \
-H "Authorization: Bearer $TOKEN"

Get Webhook Details

Terminal window
curl https://registry.example.com/api/v1/webhooks/webhook-789 \
-H "Authorization: Bearer $TOKEN"

Response includes delivery statistics:

{
"id": "webhook-789",
"url": "https://your-service.example.com/webhook",
"events": ["artifact.uploaded"],
"active": true,
"stats": {
"total_deliveries": 1523,
"successful_deliveries": 1521,
"failed_deliveries": 2,
"last_delivery_at": "2026-02-01T12:34:56Z",
"last_delivery_status": 200
}
}

Update Webhook

Terminal window
curl -X PUT https://registry.example.com/api/v1/webhooks/webhook-789 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"active": false
}'

Delete Webhook

Terminal window
curl -X DELETE https://registry.example.com/api/v1/webhooks/webhook-789 \
-H "Authorization: Bearer $TOKEN"

Test Webhook

Send a test event:

Terminal window
curl -X POST https://registry.example.com/api/v1/webhooks/webhook-789/test \
-H "Authorization: Bearer $TOKEN"

Sends a test payload:

{
"event": "webhook.test",
"timestamp": "2026-02-01T12:40:00Z",
"data": {
"webhook_id": "webhook-789",
"message": "This is a test webhook delivery"
}
}

Delivery Tracking

View Delivery History

Terminal window
curl https://registry.example.com/api/v1/webhooks/webhook-789/deliveries \
-H "Authorization: Bearer $TOKEN"

Response:

{
"deliveries": [
{
"id": "delivery-123",
"event": "artifact.uploaded",
"status_code": 200,
"delivered_at": "2026-02-01T12:34:56Z",
"duration_ms": 245,
"attempts": 1
},
{
"id": "delivery-124",
"event": "scan.completed",
"status_code": 500,
"delivered_at": "2026-02-01T12:35:00Z",
"duration_ms": 30000,
"attempts": 3,
"next_retry_at": "2026-02-01T13:05:00Z"
}
]
}

Redeliver Webhook

Manually retry a failed delivery:

Terminal window
curl -X POST https://registry.example.com/api/v1/webhooks/webhook-789/deliveries/delivery-124/redeliver \
-H "Authorization: Bearer $TOKEN"

Use Cases

CI/CD Integration

Trigger builds when artifacts are uploaded:

app.post('/webhook', (req, res) => {
const { event, data } = req.body;
if (event === 'artifact.uploaded') {
triggerBuild({
package: data.package_name,
version: data.version,
artifact_url: `https://registry.example.com/artifacts/${data.artifact_id}`
});
}
res.status(200).send('OK');
});

Security Alerting

Alert on critical vulnerabilities:

app.post('/webhook', (req, res) => {
const { event, data } = req.body;
if (event === 'scan.completed' && data.vulnerabilities.critical > 0) {
sendAlert({
severity: 'critical',
message: `${data.vulnerabilities.critical} critical vulnerabilities found in ${data.package_name}:${data.version}`,
scan_url: data.report_url
});
}
res.status(200).send('OK');
});

Audit Logging

Forward events to external logging system:

app.post('/webhook', (req, res) => {
const { event, data } = req.body;
logToSplunk({
event_type: event,
timestamp: req.body.timestamp,
metadata: data
});
res.status(200).send('OK');
});

Metrics Collection

Track artifact downloads for analytics:

app.post('/webhook', (req, res) => {
const { event, data } = req.body;
if (event === 'artifact.downloaded') {
incrementMetric('artifact.downloads', {
package: data.package_name,
version: data.version,
user: data.downloader.username
});
}
res.status(200).send('OK');
});

Configuration

System Settings

Terminal window
# Enable webhooks
WEBHOOKS_ENABLED=true
# Delivery timeout
WEBHOOK_TIMEOUT_SECONDS=30
# Maximum retries
WEBHOOK_MAX_RETRIES=5
# Concurrent deliveries
WEBHOOK_CONCURRENCY=10
# Queue size
WEBHOOK_QUEUE_SIZE=1000

Rate Limiting

Limit webhook deliveries per endpoint:

Terminal window
WEBHOOK_RATE_LIMIT=100 # Max 100 deliveries per minute per webhook

Troubleshooting

Webhooks Not Firing

  1. Check webhook is active: GET /api/v1/webhooks/webhook-789
  2. Verify event type is subscribed
  3. Check filters aren’t too restrictive
  4. Review system logs for errors

Delivery Failures

  1. Check endpoint is reachable: curl -X POST $WEBHOOK_URL
  2. Verify SSL certificate is valid
  3. Check firewall/network rules
  4. Review delivery history for error details

High Latency

  1. Ensure webhook endpoint responds quickly
  2. Process events asynchronously
  3. Increase WEBHOOK_TIMEOUT_SECONDS if needed
  4. Monitor webhook delivery metrics