Agent skill
JFrog Webhooks Skill
Receive and authenticate JFrog Artifactory and Xray platform webhooks. Use when building an endpoint for JFrog webhooks, verifying the X-JFrog-Event-Auth secret token, debugging 401s from a JFrog webhook, or handling events like artifact deployed, artifact moved, docker pushed and build uploaded.
Install this skill
npx skills add hookdeck/webhook-skills --skill jfrog-webhooks
JFrog platform webhooks notify your app when something happens in Artifactory, such as an artifact deployed, a Docker tag pushed or a build uploaded. In the default token mode, JFrog sends the secret token you configured as-is in the X-JFrog-Event-Auth header. There is no payload signature to compute: you authenticate the request by comparing that header to your secret.
When to Use This Skill
- How do I receive JFrog Artifactory webhooks?
- How do I verify the
X-JFrog-Event-Authheader on a JFrog webhook? - How do I handle
artifactdeployed/movedordockerpushedevents? - Why does my JFrog webhook endpoint get 401s or fail the Test button?
- Does JFrog sign webhook payloads? (Only in signing mode, which this skill doesn't cover.)
Verification (core)
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyJfrogRequest(headers, secret) {
if (!secret) return false; // never accept when unconfigured
const token = headers['x-jfrog-event-auth']; // the secret token, sent as-is
return Boolean(token) && safeEqual(token, secret);
}
Python: hmac.compare_. Return 401 when this fails, 400 for a body that isn't a JSON object, and 200 once handled.
Signing mode is not covered. The webhook form also has a "use secret for signing" option (use_) that signs the payload with HMAC-SHA256 instead of sending the secret. JFrog's docs don't say which header carries that signature, so this skill only supports token mode (the default, signing off). See references/verification.md.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/. The Express example reads the body with
express.raw({ type: '*/*' })so any content type is accepted.
Event Types
Every payload has a domain and an event_type; dispatch on both.
| domain | event_type | Triggered When |
|---|---|---|
artifact | deployed | An artifact is deployed to a repository |
artifact | deleted | An artifact is deleted from a repository |
artifact | moved | An artifact is moved to another repository |
docker | pushed | A Docker or OCI image tag is pushed |
build | uploaded | A new build is uploaded |
build | promoted | A build is promoted |
Other domains (artifact copied/cached, release_bundle, distribution, xray_scan_status, app_trust, ...) are listed in references/overview.md.
Environment Variables
JFROG_WEBHOOK_SECRET=replace-with-a-long-random-secret # the "Secret token" set on the webhook
Local Development
JFrog can't reach localhost (and blocks private-network targets by default). Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 jfrog --path /webhooks/jfrog
Use the printed URL as the webhook's URL.
Resources
- references/overview.md - How JFrog webhooks work, domains, events and payloads
- references/setup.md - Create the webhook and set the secret token
- references/verification.md - Token check, gotchas, signing mode caveat, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests