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
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
- Navigate to Settings > Webhooks
- Click “Create Webhook”
- Enter webhook URL and select events
- Configure filters (optional)
- 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=5d7e8f9a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8eVerify in your webhook handler:
import hmacimport 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 repositoryformat: Package format (docker, maven, npm, etc.)severity: For scan.completed eventsuser_id: Specific user actions
Webhook Delivery
Request Format
Webhooks are sent as HTTP POST requests:
POST /webhook HTTP/1.1Host: your-service.example.comContent-Type: application/jsonX-Webhook-ID: webhook-789X-Webhook-Event: artifact.uploadedX-Webhook-Signature: sha256=5d7e8f9a...X-Webhook-Delivery: delivery-123
{ "event": "artifact.uploaded", "timestamp": "2026-02-01T12:34:56Z", "data": { ... }}Headers
Content-Type: Alwaysapplication/jsonX-Webhook-ID: Webhook configuration IDX-Webhook-Event: Event typeX-Webhook-Signature: HMAC signature (if secret configured)X-Webhook-Delivery: Unique delivery attempt IDUser-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):
- Retry after 1 minute
- Retry after 5 minutes
- Retry after 30 minutes
- Retry after 2 hours
- Retry after 6 hours
- Give up after 24 hours
Configure retry behavior:
WEBHOOK_MAX_RETRIES=5WEBHOOK_RETRY_BACKOFF=exponential # or 'linear'Managing Webhooks
List Webhooks
curl https://registry.example.com/api/v1/webhooks \ -H "Authorization: Bearer $TOKEN"Get Webhook Details
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
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
curl -X DELETE https://registry.example.com/api/v1/webhooks/webhook-789 \ -H "Authorization: Bearer $TOKEN"Test Webhook
Send a test event:
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
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:
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
# Enable webhooksWEBHOOKS_ENABLED=true
# Delivery timeoutWEBHOOK_TIMEOUT_SECONDS=30
# Maximum retriesWEBHOOK_MAX_RETRIES=5
# Concurrent deliveriesWEBHOOK_CONCURRENCY=10
# Queue sizeWEBHOOK_QUEUE_SIZE=1000Rate Limiting
Limit webhook deliveries per endpoint:
WEBHOOK_RATE_LIMIT=100 # Max 100 deliveries per minute per webhookTroubleshooting
Webhooks Not Firing
- Check webhook is active:
GET /api/v1/webhooks/webhook-789 - Verify event type is subscribed
- Check filters aren’t too restrictive
- Review system logs for errors
Delivery Failures
- Check endpoint is reachable:
curl -X POST $WEBHOOK_URL - Verify SSL certificate is valid
- Check firewall/network rules
- Review delivery history for error details
High Latency
- Ensure webhook endpoint responds quickly
- Process events asynchronously
- Increase
WEBHOOK_TIMEOUT_SECONDSif needed - Monitor webhook delivery metrics