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-Auth header on a JFrog webhook?
  • How do I handle artifact deployed / moved or docker pushed events?
  • 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_digest(received.encode(), secret.encode()). 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_secret_for_signing=true) 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.

domainevent_typeTriggered When
artifactdeployedAn artifact is deployed to a repository
artifactdeletedAn artifact is deleted from a repository
artifactmovedAn artifact is moved to another repository
dockerpushedA Docker or OCI image tag is pushed
builduploadedA new build is uploaded
buildpromotedA 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 3, 2026

View on GitHub →