Gareth Wilson Gareth Wilson

Guide to JFrog Webhooks: Features and Best Practices

Published


JFrog webhooks tell your systems what is happening across the JFrog Platform: an artifact is deployed, a Docker tag is pushed, a build is promoted, a release bundle is distributed, an Xray scan finishes, or an application version clears an AppTrust gate. If you run Artifactory or the wider JFrog Platform, webhooks let CI/CD, deployment and security tooling react to those events instead of polling.

This guide covers predefined JFrog webhooks: how they are configured, how the secret token authenticates them, the domains and events they cover, what JFrog leaves undocumented, and the practices that keep a JFrog receiver reliable.

What are JFrog webhooks?

JFrog webhooks are sent by the JFrog Event Service, which delivers events asynchronously. There are two kinds:

  • Predefined webhooks send a fixed JSON payload for each domain and event. This guide covers these.
  • Custom webhooks let you template the method, headers and body for a particular target service. Their format is whatever you configure.

A predefined webhook covers one domain, such as artifact, docker or build, and a selection of event types within it. Every payload names both, so the event is identified by the pair, for example artifact + deployed.

Authentication is optional and depends on how you set the webhook's secret. By default, JFrog sends the secret token unchanged in an X-JFrog-Event-Auth header, and your receiver compares it with the value it expects. JFrog also offers a signing mode, in which the secret signs the payload with HMAC-SHA256 and is not sent, but its documentation does not say which header carries that signature.

JFrog webhook features

FeatureDetails
ConfigurationPlatform > Integrations > Webhooks (all projects or one project), or POST /event/api/v1/subscriptions
TypesPredefined (fixed payload) or custom (your template)
MethodPOST for predefined webhooks
ScopeOne domain per webhook, with chosen event types and resource criteria
Project webhooksLimited to Artifacts, Docker, Artifact Properties and Builds
AuthenticationOptional secret token, sent verbatim in X-JFrog-Event-Auth (signing off, the API default)
Signing modeHMAC-SHA256 of the payload, header and encoding not documented
Event identitydomain + event_type in the body
Custom headersAny number, plus encrypted secrets injected with {{.secrets.<name>}}
TestingA Test button sends a fake request
TroubleshootingSuccessful executions can be shown in a Troubleshooting tab where available
Private targetsBlocked by default on self-hosted installs (urlStrictPolicy)

JFrog does not document a response timeout, retry policy, event id, timestamp or source IP list for webhooks.

Common events

JFrog documents 62 events with a domain and event_type across 12 domains, plus four Curation events.

DomainEvent types
artifactdeployed, deleted, moved, copied, cached
artifact_propertyadded, deleted
dockerpushed, deleted, promoted
builduploaded, deleted, promoted
release_bundlecreated, signed, deleted
distributiondistribute_started, distribute_completed, distribute_aborted, distribute_failed, delete_started, delete_completed, delete_failed
destinationreceived, delete_started, delete_completed, delete_failed
userlocked
release_bundle_v2release_bundle_v2_started, release_bundle_v2_failed, release_bundle_v2_completed
release_bundle_v2_promotionrelease_bundle_v2_promotion_started, release_bundle_v2_promotion_failed, release_bundle_v2_promotion_completed
xray_scan_statusdone, failed, partial, not_supported
app_trust24 events for entry and exit gate evaluations, application creation, update and deletion, version creation and promotion, and releases, each _started, _completed or _failed (gates use _validation_passed and _validation_failed)

event_type alone is ambiguous: deleted exists in five domains. Always dispatch on both fields. The four Curation events (a package blocked by Curation, a waiver request created or updated, and a policy change) have payloads with no domain or event_type, so you can only recognise them by their shape.

JFrog's Distribution section headings say deletion_started, but its example payloads say delete_started. Match the example value.

Here is JFrog's artifact deployed example:

{
  "domain": "artifact",
  "event_type": "deployed",
  "data": {
    "repo_key": "sample_repo",
    "path": "sample_dir/sample.txt",
    "name": "sample.txt",
    "sha256": "sample_checksum",
    "size": 0
  },
  "subscription_key": "test",
  "jpd_origin": "https://<your_origin>",
  "source": "jfrog/<your_source>",
  "userContext": {
    "id": "johndoe",
    "isToken": false,
    "realm": "internal"
  }
}

data holds the domain-specific object. subscription_key names the webhook, jpd_origin is your platform URL, and userContext identifies the user or token that caused the event. JFrog documents no event id, so there is no built-in deduplication key.

Setting up JFrog webhooks

  1. Choose All Projects, or a specific project, then open Platform > Integrations > Webhooks and click New Webhook.
  2. Make sure the Predefined toggle is selected.
  3. Enter a name, a description and the URL JFrog should call.
  4. Select the events, then the resources each applies to, such as repositories, builds or AppTrust applications.
  5. Enter a secret token, and leave the option to use the secret for signing off. With signing off, JFrog sends the token in X-JFrog-Event-Auth on every request.
  6. Add any extra HTTP headers you need, click Test, then Create.

If you use Hookdeck, set the same token on your JFrog source. Hookdeck supports token mode only: it checks X-JFrog-Event-Auth and cannot verify JFrog's payload signing mode, so keep signing off. If you don't set a secret on the JFrog webhook, leave verification off on the source too.

Through the REST API, the same settings are handlers[].secret and handlers[].use_secret_for_signing, which defaults to false.

Securing JFrog webhooks

In token mode there is nothing to compute. Read X-JFrog-Event-Auth and compare it with your secret token in constant time. Because the body is not signed, you can parse it normally.

const crypto = require('crypto');
const express = require('express');

function verifyJfrogToken(headerValue, expectedToken) {
  if (!headerValue || !expectedToken) return false;
  const received = Buffer.from(headerValue, 'utf8');
  const expected = Buffer.from(expectedToken, 'utf8');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

const app = express();

app.post('/webhooks/jfrog', express.raw({ type: '*/*' }), (req, res) => {
  const token = process.env.JFROG_WEBHOOK_SECRET;
  if (!token) return res.sendStatus(500); // fail closed

  if (!verifyJfrogToken(req.get('x-jfrog-event-auth'), token)) {
    return res.sendStatus(401);
  }

  let event;
  try {
    event = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }
  // Dispatch on `${event.domain}.${event.event_type}`; Curation payloads have neither.
  res.sendStatus(200);
});

The same check in Python:

import hmac


def verify_jfrog_token(header_value, expected_token: str) -> bool:
    if not header_value or not expected_token:
        return False
    return hmac.compare_digest(header_value.encode("utf-8"), expected_token.encode("utf-8"))


# In FastAPI: verify_jfrog_token(request.headers.get("x-jfrog-event-auth"),
#                                os.environ["JFROG_WEBHOOK_SECRET"])

The token is a static credential sent with every request. Anyone who captures one request can forge others, so serve the endpoint over HTTPS only, use a long random token, and rotate it if it might have leaked.

Signing mode

With use secret for signing on, JFrog stops sending the token and signs the payload with HMAC-SHA256 instead. Its docs show how to check that a payload and signature match (echo -n '<actual payload>' | openssl sha256 -hmac "<secret>") but not which header carries the signature or how it is encoded. Capture a real delivery and confirm the header before you build on signing mode.

Make JFrog webhooks production-ready. Hookdeck Event Gateway checks the X-JFrog-Event-Auth token, answers JFrog with an immediate 200 OK, and durably queues every artifact, build and release event before your service sees it.

JFrog webhook limitations and pain points

A static token instead of a signature

The Problem: The credential travels with every request, and nothing protects the body from being changed.

Why It Happens: In token mode, JFrog sends the secret itself in X-JFrog-Event-Auth. The body is not signed and there is no timestamp, so a captured request can be replayed or altered.

Workarounds:

  • Use HTTPS, a long random token, and rotate it periodically.
  • Treat the webhook as a notification and confirm important details, such as a build's status, through the JFrog API before acting.

How Hookdeck Can Help: Hookdeck checks the token, so your own services never need to hold it, and stops requests with a missing or wrong token before they reach your service.

Undocumented signing mode

The Problem: Turning on signing breaks verification.

Why It Happens: With signing on, JFrog no longer sends X-JFrog-Event-Auth, and the header that carries the signature is not documented. Receivers that check the token reject every request, and you have nothing documented to check instead.

Workarounds:

  • Keep use secret for signing off unless you have confirmed the signature header from a real delivery.
  • If you need signing, verify your implementation against captured requests first.

How Hookdeck Can Help: Hookdeck's JFrog source supports token mode. Keep signing off, set the token on the source, and Hookdeck authenticates each request.

No delivery guarantees documented

The Problem: You don't know whether a failed delivery will be retried, or how long JFrog waits for a response.

Why It Happens: JFrog's webhook pages describe asynchronous delivery through the Event Service but no timeout, retry schedule or success criteria.

Workarounds:

  • Respond quickly with a 2xx and process asynchronously.
  • Turn on the Troubleshooting tab where it is available, to see executions.
  • Reconcile important state through the JFrog API after an outage.

How Hookdeck Can Help: Hookdeck accepts each event as soon as it arrives and stores it, then retries delivery to your service on a schedule you control and lets you replay anything that failed.

No event id

The Problem: You can't reliably tell a duplicate from a new event.

Why It Happens: JFrog payloads carry no event id or timestamp.

Workarounds:

  • Build a key from domain, event_type and identifying data fields, such as repo_key and path with sha256 for artifacts, or build name and number for builds.
  • Make handlers idempotent, so a repeated deploy event does no harm.

How Hookdeck Can Help: Hookdeck's deduplication can drop repeats based on the body fields you choose.

Best practices

Dispatch on domain and event_type together

deleted means different things in five domains. Build a key such as artifact.deleted and route on that.

Recognise Curation payloads by shape

Curation events have no domain or event_type. If you subscribe to them, check for their distinctive fields before falling through to "unknown event".

Narrow webhooks with criteria

Limit each webhook to the repositories, builds or applications you care about, using include and exclude patterns. Selecting "any local repository" also covers repositories created later, which can grow traffic over time.

Make the endpoint reachable

JFrog Cloud can't reach localhost, and self-hosted installs block private-network targets by default. Use a public HTTPS URL, or a tunnel such as the Hookdeck CLI during development.

Acknowledge fast and process asynchronously

Return 200 once the token is checked and the event is stored, then do the work. See why to process webhooks asynchronously and our guide to webhook idempotency.

Conclusion

Predefined JFrog webhooks send a JSON payload identified by domain and event_type, authenticated by a secret token in X-JFrog-Event-Auth when signing is off. Keep signing off unless you have confirmed its undocumented header, compare the token in constant time over HTTPS, dispatch on both fields, build your own deduplication key, and acknowledge quickly, since JFrog documents no retry policy.

Hookdeck Event Gateway sits in front of your receiver, checks the JFrog token, and holds each event until your service has processed it.

Get started with Hookdeck for free and handle JFrog webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.