Skip to content

Move Artifactory and Nexus webhooks to CloudRepo

View as Markdown

If a Jenkins job, a chat message or a script runs when Artifactory or Nexus posts a webhook, this page maps each of their events and payload fields to CloudRepo’s, so you can rewire it one event at a time. A move copies your files and not your webhooks, so you create each webhook again in CloudRepo: Webhooks shows the form.

CloudRepo has three file events, upload, download and delete, and posts each one as JSON to an https:// address (or as a message to Slack). This page covers the JSON webhook, the one a build job listens to.

Artifactory names an event by a domain and a type, and a webhook picks the ones it fires on. JFrog lists them on Webhook Event Types. Here is each artifact and Docker event beside the CloudRepo event that replaces it, and the domains CloudRepo has nothing for.

Artifactory event CloudRepo event What to know
Artifact deployed upload-complete Tick Upload on the CloudRepo webhook.
Artifact deleted delete-file-complete Tick Delete. A Delete webhook also receives other event-type values from portal actions such as deleting a folder or restoring from the Trash, so filter on event-type.
Docker pushed upload-complete A tag push fires the upload webhooks. A pull, a layer upload and a push by digest fire none.
Docker deleted delete-file-complete Deleting a tag in the portal, or a tag or an image from a registry client, fires the delete webhooks.
Artifact moved or copied No match CloudRepo has no event for either.
Artifact cached No separate cached event CloudRepo has no event named cached; its three events are upload, download and delete.
Artifact property added or deleted No match CloudRepo has no events for artifact properties.
Docker promoted No match CloudRepo has no event for a promotion.
Build, Release Bundle, Distribution, Curation, Xray scan, AppTrust and user events No match CloudRepo’s webhooks are for file events in a repository.
None listed download-complete JFrog’s page lists no download event. Tick Download to receive one.

Webhooks says what a Docker repository fires and what it does not, and that a webhook on a Maven group’s member repository, not on the group, hears a download through the group.

A Nexus repository webhook fires on asset events or on component events, and the payload’s action says what happened. Sonatype’s Example Headers And Payloads shows CREATED. The other actions, UPDATED, DELETED and PURGED, are in Sonatype’s published source.

Nexus event CloudRepo event What to know
Asset CREATED upload-complete One event for each file.
Component CREATED upload-complete CloudRepo has no component event. The upload event is for the file.
Asset DELETED delete-file-complete A Delete webhook also receives other event-type values, so filter on event-type, as the Artifactory table says.
Component DELETED delete-file-complete CloudRepo has no component event.
Asset or component UPDATED or PURGED No match CloudRepo has no event for either.
Global webhooks (audit and repository events) No match A CloudRepo webhook belongs to one repository.
None listed download-complete Sonatype’s pages list no download event for repository webhooks. Tick Download to receive one.

Sonatype shows two repository payloads, asset and component, whatever the format, and asset.format or component.format in the payload names the format. In CloudRepo a Docker tag push is upload-complete and deleting a tag or an image is delete-file-complete, whichever vendor you come from.

Both vendors post a JSON body, and its field names differ from CloudRepo’s. JFrog’s come from Webhook Event Types and Sonatype’s from Example Headers And Payloads; a cell reads None listed where the vendor’s examples do not show the field. CloudRepo’s are the fields of a JSON webhook (a Slack webhook posts a text message instead).

What you read Artifactory Nexus CloudRepo
Which event domain and event_type The X-Nexus-Webhook-Id header and action event-type
Repository data.repo_key repositoryName repository-id
File name and path data.name and data.path asset.name file-name and file-path
Size in bytes data.size None listed file-size-bytes
Checksum data.sha256 None listed No match
Who did it userContext.id initiator, which Sonatype’s example shows as a user and an address user-id
Where from None listed initiator, in the same example ip-address and user-agent
When None listed timestamp event-timestamp
Public or private repository None listed None listed repository-visibility
Links to the file None listed None listed private-url, public-url and admin-url
Docker image and tag data.image_name and data.tag None listed file-name, written as image:tag
Component group and version None listed component.group and component.version No match
Where the event came from subscription_key, jpd_origin and source nodeId and the X-Nexus-Webhook-Delivery header No match
Other ids and details userContext.isToken, userContext.realm, data.platforms, data.image_type asset.id, asset.assetId, component.id, component.componentId, asset.format, component.format No match

JFrog’s data.path ends in the file name. CloudRepo’s file-path is the directory and file-name is the file, so JFrog’s path is file-path, a slash, then file-name, unless file-path is empty.

CloudRepo sends no checksum, so a job that keys on data.sha256 has nothing to read.

A CloudRepo event carries these fields and no others. yes means the event sends the field.

CloudRepo field Upload Download Delete
event-type yes yes yes
user-id yes yes yes
repository-visibility yes yes yes
repository-id yes yes yes
file-name yes yes yes
file-path yes yes yes
event-timestamp yes yes yes
file-size-bytes yes yes no
private-url yes no no
public-url yes, public repositories only no no
admin-url yes no no
ip-address yes yes yes
user-agent yes yes yes

An upload to a private repository posts this body:

An upload event
{
"event-type": "upload-complete",
"user-id": "you@example.com",
"repository-visibility": "private",
"repository-id": "your-repo",
"file-name": "app-1.0.jar",
"file-path": "com/acme/app/1.0",
"event-timestamp": "2026-10-23T12:00:00Z",
"file-size-bytes": 48213,
"private-url": "https://your-org.mycloudrepo.io/repositories/your-repo/com/acme/app/1.0/app-1.0.jar",
"admin-url": "https://admin.cloudrepo.io/browse/your-repo/com/acme/app/1.0",
"ip-address": "203.0.113.7",
"user-agent": "Apache-Maven/3.9.9"
}
  • user-id is the user’s email address, or Anonymous for an anonymous download of a public repository.
  • repository-visibility is private or public.
  • event-timestamp is an ISO 8601 time in UTC.
  • In a Docker repository file-name is the image and the tag, such as app:1.0 (a delete by digest names the image and the digest), and file-path is empty. An upload’s private-url is the registry address of the tag’s manifest. A delete carries no URL.

These are settings a vendor’s webhook has and CloudRepo’s does not. JFrog describes its in Predefined Webhooks and Custom Webhooks; Sonatype describes its in Enabling A Repository Webhook Capability and Working With HMAC Payloads.

Setting Artifactory Nexus CloudRepo
Signing the request An optional secret token. JFrog names the X-JFrog-Event-Auth header and shows an HMAC check An optional secret key. Sonatype describes an HMAC SHA-1 digest of the body in the X-Nexus-Webhook-Signature header No match
Your own headers, body or method Custom webhooks set all three None listed No match
A delivery log A Troubleshooting tab None listed No match

CloudRepo does not sign a request or send a secret in a header, so there is nothing to verify on your side. It sends each event once, as a POST with the JSON body above. It does not retry a failed delivery and it shows you no log of deliveries, so a listener that is down misses the event.

CloudRepo posts to the address you register exactly as you wrote it, query string included. If your listener wants a token, put it in the query string, as the Jenkins plugin below reads its token from there, and treat the whole address as a secret, as you would a Slack webhook URL.

A Jenkins job that runs on an Artifactory or Nexus webhook reads the repository and the file out of the body. This example uses the Generic Webhook Trigger plugin, which pulls values out of a JSON body with JSONPath, so rewiring the job is two changes: the CloudRepo webhook that posts to it, and the paths its Jenkinsfile reads.

JFrog’s own Jenkins recipe, on its Custom Webhooks page, shapes a form body for Jenkins’s buildWithParameters address. CloudRepo’s body is fixed, so that recipe has no twin: read the CloudRepo body with the plugin instead.

Before you start:

  • CloudRepo posts to your Jenkins from outside your network, so the address of your trigger must be an https:// URL that resolves to a public host. CloudRepo does not follow a redirect, so give the final address.
  • Keep the trigger’s token in a Jenkins secret text credential. The Jenkinsfile names that credential with tokenCredentialId and holds no token.

Create it as Webhooks shows: choose the repository, set Webhook Target to your Jenkins’s generic-webhook-trigger/invoke address followed by ?token= and the trigger’s token, set the type to JSON, tick Upload and click Create Webhook.

An Artifactory job reads the repository and the path like this:

Jenkinsfile, before (Artifactory)
pipeline {
agent any
triggers {
GenericTrigger(
genericVariables: [
[key: 'REPO', value: '$.data.repo_key'],
[key: 'FILE_PATH', value: '$.data.path']
],
causeString: 'Artifact deployed to $REPO',
tokenCredentialId: 'cloudrepo-webhook-token'
)
}
stages {
stage('Show') {
steps {
echo "New file: ${env.REPO}/${env.FILE_PATH}"
}
}
}
}

After the move it reads the three CloudRepo fields instead:

Jenkinsfile, after (CloudRepo)
pipeline {
agent any
triggers {
GenericTrigger(
genericVariables: [
[key: 'REPO', value: '$[\'repository-id\']'],
[key: 'DIR', value: '$[\'file-path\']'],
[key: 'FILE', value: '$[\'file-name\']']
],
causeString: 'File uploaded to $REPO',
tokenCredentialId: 'cloudrepo-webhook-token'
)
}
stages {
stage('Show') {
steps {
echo "New file: ${env.REPO}/${env.DIR}/${env.FILE}"
}
}
}
}

A Nexus job reads $.repositoryName and $.asset.name where the Artifactory job reads $.data.repo_key and $.data.path; both become the three paths above.

A file name and a path are whatever the uploader chose, so use them as values, as the example does, and make sure they are not pasted into a shell command.

Run the pipeline once by hand. Jenkins applies a trigger written in a Jenkinsfile only after the pipeline has run. Then upload one file to the repository and watch the job start. When it does, delete the Artifactory or Nexus webhook.