Nuxeo / DAM / PAM / ECM specialistsContact
Home/Insights/Nuxeo
Insights

Creating an Open POST Endpoint in Nuxeo for external integrations

Jul 10, 20262 min read

Creating an Open POST Endpoint in Nuxeo for external integrations

I'm back from my summer vacation with a quick and hopefully useful tip!

Whether you're using Nuxeo as an ECM or DAM in your organization, there's almost always a need to integrate with external systems.

Sometimes, those systems need to call a publicly accessible endpoint on your Nuxeo server-like to notify you when a job is done or to push data back into the platform.

Let me show you how easy it is to create such an endpoint using Nuxeo WebEngine with JAX-RS.

Use Case

In this example, I was working on an integration with an external archiving system. We submit restore requests from Nuxeo, and when the restore is completed on their end, their system needs to call an open URL on the Nuxeo side to let us know that the job is finished.

The endpoint must support:

  • Unauthenticated access
  • A POST request
  • A custom JSON payload

Objective

We want to add an open, unauthenticated endpoint that accepts a POST request like this:

POST http://localhost:8080/nuxeo/site/syncstatus/update

With the payload:

{
  "jobId": "3da5bea3-4a09-3180-8c72-ad290b80f08c",
  "eventClass": "JOB",
  "status": "COMPLETE"
}

Overview of the Flow:

External System
   │
   ▼
POST /site/syncstatus/update
   │
   ▼
Nuxeo processes custom payload

Step 1: Contribute the Open URL

Nuxeo supports JAX-RS via WebEngine, which is exactly what we'll use to expose this API: 📘 https://doc.nuxeo.com/nxdoc/webengine-jax-rs/

In your XML contribution, register the open URL:

<extension point="openUrl"
    target="org.nuxeo.ecm.platform.ui.web.auth.service.PluggableAuthenticationService">
  <openUrl name="syncstatus">
    <grantPattern>${org.nuxeo.ecm.contextPath}/site/syncstatus</grantPattern>
  </openUrl>
</extension>

Step 2: Create the Endpoint Class

Create a new class extending ModuleRoot, mapped to /syncstatus. 📘 Reference: ModuleRoot.java

@Path("/syncstatus")
@Produces("text/html;charset=UTF-8")
@WebObject(type = "syncstatus")
/**
 * Open endpoint to receive callback
 */
public class SyncStatus extends ModuleRoot {

    public static final Log log = LogFactory.getLog(SyncStatus.class);

    @Context
    private HttpServletRequest request;

    @POST
    @Path("update")
    @Produces(MediaType.APPLICATION_JSON)
    public Response syncJob(JobPayload request) {
        return syncJob(request.getJobId(), request.getEventClass());
    }
}

Step 3: Create the Payload Class

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class JobPayload {

    private String jobId;
    private String eventClass;
    private String status;

    public JobPayload() {}

    public String getJobId() { return jobId; }
    public void setJobId(String jobId) { this.jobId = jobId; }

    public String getEventClass() { return eventClass; }
    public void setEventClass(String eventClass) { this.eventClass = eventClass; }

    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
}

Step 4: Register the Custom JSON Reader

@Provider
@Consumes(MediaType.APPLICATION_JSON)
public class JobPayloadReader implements MessageBodyReader<JobPayload> {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return JobPayload.class.isAssignableFrom(type)
            && MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType);
    }

    @Override
    public JobPayload readFrom(Class<JobPayload> type, Type genericType,
                               Annotation[] annotations, MediaType mediaType,
                               MultivaluedMap<String, String> httpHeaders,
                               InputStream entityStream) throws IOException {

        return objectMapper.readValue(entityStream, JobPayload.class);
    }
}

Done!

That's it! You now have an open Nuxeo endpoint that accepts POST requests with a custom JSON payload.

Notes on Security

  • Only use unauthenticated endpoints when absolutely necessary.
  • If the endpoint should be private, simply don't contribute it via openUrl.

Bonus: Test It with cURL

curl -X POST http://localhost:8080/nuxeo/site/syncstatus/update \
  -H "Content-Type: application/json" \
  -d '{
    "jobId": "3da5bea3-4a09-3180-8c72-ad290b80f08c",
    "eventClass": "JOB",
    "status": "COMPLETE"
  }'

Let me know if you've built similar integrations or want to explore more advanced patterns like token auth or validation.

← All insights

Keep reading

Related insights

Talk to a Maretha Consultant

Tell us what you're struggling with, and we'll tell you how we can help you.

Talk to us