Move Artifactory and Nexus webhooks to CloudRepo
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 events
Section titled “Artifactory events”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.
Nexus events
Section titled “Nexus events”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.
Payload fields
Section titled “Payload fields”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.
Fields by CloudRepo event
Section titled “Fields by CloudRepo event”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:
{ "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-idis the user’s email address, orAnonymousfor an anonymous download of a public repository.repository-visibilityisprivateorpublic.event-timestampis an ISO 8601 time in UTC.- In a Docker repository
file-nameis the image and the tag, such asapp:1.0(a delete by digest names the image and the digest), andfile-pathis empty. An upload’sprivate-urlis the registry address of the tag’s manifest. A delete carries no URL.
What has no match
Section titled “What has no match”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.
Rewire a Jenkins job
Section titled “Rewire a Jenkins job”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
tokenCredentialIdand holds no token.
Create the webhook in CloudRepo
Section titled “Create the webhook in CloudRepo”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.
Change the paths the Jenkinsfile reads
Section titled “Change the paths the Jenkinsfile reads”An Artifactory job reads the repository and the path like this:
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:
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 it
Section titled “Run it”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.