This is some text inside of a div block.
To the overview
EUDR
Nov 19, 2025
5 min
LESEDAUER

Integrating your ERP with the EUDR TRACES API: Challenges and modern alternatives

Zwei Nachhaltigkeitsmanager die eine Visite machen

Understanding EUDR and the TRACES API

The EU Deforestation Regulation (EUDR) is a landmark law requiring companies to prove that certain commodities (like wood, coffee, soy, etc.) are deforestation-free. To comply, operators and traders must submit Due Diligence Statements (DDS) for each relevant shipment or batch. All DDS submissions happen through the EU’s TRACES platform (Trade Control and Expert System), an online system where businesses register and send these compliance statements to authorities.

If your company needs to comply with EUDR, you’ll likely have to integrate your internal systems – such as your ERP – with the EUDR TRACES API to automate the submission of DDS forms.

However, working with the official TRACES API is easier said than done. It’s very poorly documented, and follows the highly convoluted SOAP standard. This means integrating your ERP system with EUDR’s TRACES API can quickly become a headache in terms of development effort and maintenance.

EUDR TRACES - Why the API integration is challenging

From a developer’s perspective, integrating with the TRACES SOAP API is tedious and complex. The API’s age and design show through in several ways:

  • Legacy SOAP & XML Protocol: The TRACES API is built on SOAP, a protocol considered archaic by today’s standards. Modern developers accustomed to JSON/REST find SOAP very verbose and rigid, requiring you to wrap data in lengthy XML envelopes. Handling XML parsing/generation adds overhead and feels like “stepping back in time” for those used to lightweight JSON APIs.
  • Complex Authentication (WS-Security): Authenticating to TRACES isn’t as simple as an API token in a header. Instead, you must compute digests and include precise security headers and tokens into the SOAP XML (using WS-Security standards) for each request. Crafting these headers correctly (with timestamps, usernames, and password tokens) is non-trivial and a common point of failure.
  • Manual XML Construction: Every DDS submission from your ERP data has to be converted into the exact XML schema that TRACES expects. This manual XML construction is tedious and error-prone. A small formatting mistake can cause the whole request to fail. Developers often spend more time wrestling with XML schemas and fixing syntax than writing actual business logic.
  • Difficult Error Handling: When something goes wrong, the TRACES API returns SOAP Fault messages buried in XML. These error messages can be cryptic and difficult to interpret. Debugging issues (e.g. why a submission failed) takes longer compared to the more readable JSON errors in REST APIs. This slows down development and testing cycles.
  • Limited Documentation & Rigid Workflow: The TRACES API is very poorly documented, and has frequently changed in the past. Any update or change on the EU side means re-generating SOAP client code or adjusting your XML, which is cumbersome.

An ever-changing target: TRACES API versions and updates

Another complication is that the EUDR TRACES API has changed multiple times (and will likely change again). Since EUDR’s rollout is recent, the European Commission has been iterating on the API specifications. In fact, early implementers were warned that the technical specs were not final and would be subject to change, with new versions released as needed. This means the XML schema or methods you coded against a few months ago might be updated, requiring you to adjust your integration.

Moreover, policy updates can introduce new requirements. For example, in late 2025 the EU discussed “simplified” due diligence for certain traders, but as of now the only way to automate EUDR compliance is still the original SOAP services. Eventually, new API endpoints or data fields will be added for these simplified procedures, and companies will need to adapt their systems again to handle new transaction types. In other words, integrating with TRACES isn’t a one-and-done project – you have to be prepared to maintain and update your solution as the API evolves.

Integrating your ERP System with a SOAP API (General approach)

Despite the challenges, integrating an ERP with the EUDR TRACES API is doable with careful planning. The goal is to have your ERP automatically send Due Diligence Statements to TRACES whenever you dispatch or receive a regulated commodity shipment. Here are general steps and considerations for an ERP–TRACES integration:

  1. Extracting Data from the ERP: First, you need to gather all required DDS data from your ERP. This includes details like the commodity type and quantity (with HS codes), supplier information, geolocation coordinates of origin, and any referenced previous DDS (for downstream traders). Ensure your ERP has fields or can generate reports for all information mandated by EUDR compliance. Some customization might be required in your ERP to collect geolocation data or store DDS reference numbers for incoming goods.
  2. Building the SOAP Request: Using the data above, your integration layer must construct the SOAP XML message exactly as expected by TRACES. This means wrapping the data in the correct XML tags defined by the TRACES WSDL (Web Service Definition). You will include your authentication in the SOAP header (username and the special API key in a WS-Security <UsernameToken>). Many development environments allow you to import the WSDL and generate SOAP client classes – for example, using Java tools (JAX-WS or Apache CXF) to create stub objects for requests. Alternatively, you can craft the XML manually or with an XML library.
  3. Sending the Request and Receiving Response: The integration will then send the SOAP message over HTTPS to the TRACES API endpoint (different URLs exist for the test “ACCEPTANCE” environment vs. the production server). The response, if successful, will contain a DDS Reference Number (a unique ID) for the submitted statement. Your system should capture this ID and store it in your ERP (e.g., linked to the shipment record) because it needs to accompany the physical shipment and be available for auditors or customs. If the response is a fault (error), your integration should log it and handle it – possibly retrying or flagging for manual review.
  4. Handling Updates and Queries: The TRACES API may also offer operations to retrieve a DDS or update one (if allowed). Your ERP integration might need to poll or fetch status updates, or to implement amendments/corrections by sending another SOAP call. In practice, at minimum you must submit new DDS and log the reference IDs. Keep an eye on any new versions of the API for added functionality (for example, if in the future the API allows querying by reference number to verify an existing DDS, you might want to use that to automate checks on incoming goods).

Example: Creating a Due Diligence Statement directly using the Traces API in Java

To illustrate the complexity, below is a simplified Java snippet that creates a SOAP message for a single endpoint in the TRACES API.

This example uses Java’s SOAP libraries to construct a submitDDS request with a dummy structure. In a real scenario, you would use actual field values from your ERP and the official namespace/element names from the WSDL. (Note: In practice, you might generate proxy classes from the WSDL, we’re showing the raw SOAP structure here for context)


import javax.xml.soap.*;
import javax.xml.namespace.QName;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.UUID;

public class TracesEudrClient {
    
    private static final String TRACES_BASE_URL = "https://acceptance.eudr.webcloud.ec.europa.eu";
    private static final String SUBMISSION_SERVICE_PATH = "/tracesnt/ws/EUDRSubmissionServiceV2";
    private static final String WEB_SERVICE_CLIENT_ID = "eudr-test"; // "eudr-repository" for production
		private static final int TIMESTAMP_VALIDITY_SECONDS = 60;
		private static final int TIMEOUT_MS = 10000;

		private final String username;
		private final String authenticationKey;
    
    public TracesEudrClient(String username, String authenticationKey) {
        this.username = username;
        this.authenticationKey = authenticationKey;
    }
    

    private String generatePasswordDigest(byte[] nonceBytes, String created) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-1");
        digest.update(nonceBytes);
        digest.update(created.getBytes("UTF-8"));
        digest.update(authenticationKey.getBytes("UTF-8"));
        byte[] hash = digest.digest();
        return Base64.getEncoder().encodeToString(hash);
    }



    public SOAPMessage submitDDS(String operatorType, String statementXml) throws Exception {
        String soapEndpointUrl = TRACES_BASE_URL + ":443" + SUBMISSION_SERVICE_PATH;
        String soapAction = "http://ec.europa.eu/tracesnt/certificate/eudr/submission/v2/submitDds";
        
        // 1. Generate security tokens
        SecureRandom random = new SecureRandom();
        byte[] nonceBytes = new byte[16];
        random.nextBytes(nonceBytes);
        String nonceBase64 = Base64.getEncoder().encodeToString(nonceBytes);
        
        Date now = new Date();
        String created = formatISO8601(now);
        
        Calendar cal = Calendar.getInstance();
        cal.setTime(now);
        cal.add(Calendar.SECOND, TIMESTAMP_VALIDITY_SECONDS);
        String expires = formatISO8601(cal.getTime());
        
        String passwordDigest = generatePasswordDigest(nonceBytes, created);
        
        // Generate unique IDs for security elements
        String timestampId = "TS-" + UUID.randomUUID().toString();
        String usernameTokenId = "UsernameToken-" + UUID.randomUUID().toString();
        
        // 2. Create SOAP Connection
        SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance();
        SOAPConnection connection = scf.createConnection();
        
        // 3. Construct the SOAP Message
        MessageFactory messageFactory = MessageFactory.newInstance();
        SOAPMessage soapMessage = messageFactory.createMessage();
        SOAPPart soapPart = soapMessage.getSOAPPart();
        SOAPEnvelope envelope = soapPart.getEnvelope();
        
        // Add namespaces
        envelope.addNamespaceDeclaration("v2", "http://ec.europa.eu/tracesnt/certificate/eudr/submission/v2");
        envelope.addNamespaceDeclaration("v21", "http://ec.europa.eu/tracesnt/certificate/eudr/model/v2");
        envelope.addNamespaceDeclaration("v4", "http://ec.europa.eu/sanco/tracesnt/base/v4");
        
        // 4. Build SOAP Header with WS-Security
        SOAPHeader header = envelope.getHeader();
        
        // Add Security element with mustUnderstand="1"
        SOAPElement security = header.addChildElement("Security", "wsse", 
            "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        security.addNamespaceDeclaration("wsu", 
            "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
        security.addAttribute(envelope.createQName("mustUnderstand", "soapenv"), "1");
        
        // Add Timestamp
        SOAPElement timestamp = security.addChildElement("Timestamp", "wsu");
        timestamp.addAttribute(new QName(
            "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", 
            "Id", "wsu"), timestampId);
        timestamp.addChildElement("Created", "wsu").addTextNode(created);
        timestamp.addChildElement("Expires", "wsu").addTextNode(expires);
        
        // Add UsernameToken
        SOAPElement usernameToken = security.addChildElement("UsernameToken", "wsse");
        usernameToken.addAttribute(new QName(
            "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", 
            "Id", "wsu"), usernameTokenId);
        
        usernameToken.addChildElement("Username", "wsse").addTextNode(username);
        
        // Add Password with Type attribute
        SOAPElement password = usernameToken.addChildElement("Password", "wsse");
        password.addAttribute(new QName("Type"), 
            "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
        password.addTextNode(passwordDigest);
        
        // Add Nonce with EncodingType attribute
        SOAPElement nonce = usernameToken.addChildElement("Nonce", "wsse");
        nonce.addAttribute(new QName("EncodingType"), 
            "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
        nonce.addTextNode(nonceBase64);
        
        // Add Created timestamp to UsernameToken
        usernameToken.addChildElement("Created", "wsu").addTextNode(created);
        
        // Add WebServiceClientId header (CRITICAL - required by TRACES)
        SOAPElement clientId = header.addChildElement("WebServiceClientId", "v4");
        clientId.addTextNode(WEB_SERVICE_CLIENT_ID);
        
        // 5. Build SOAP Body
        SOAPBody body = envelope.getBody();
        SOAPElement submitRequest = body.addChildElement("SubmitStatementRequest", "v2");
        submitRequest.addChildElement("operatorType", "v2").addTextNode(operatorType); // OPERATOR, TRADER, etc.
        
        // Add statement element - you would insert your generated XML here
        SOAPElement statementElement = submitRequest.addChildElement("statement", "v2");
        // Note: You need to parse and insert the statementXml properly
        // This is a simplified example - in practice you'd need to parse the XML
        // and add it as child elements or use a DOM approach
        statementElement.addTextNode(statementXml);
        
        // Set SOAPAction header
        MimeHeaders mimeHeaders = soapMessage.getMimeHeaders();
        mimeHeaders.addHeader("SOAPAction", soapAction);
        
        soapMessage.saveChanges();
        
        // Optional: Print request for debugging
        System.out.println("=== SOAP Request ===");
        soapMessage.writeTo(System.out);
        System.out.println("\n");
        
        // 6. Send the SOAP request and get the response
        SOAPMessage response = connection.call(soapMessage, soapEndpointUrl);
        connection.close();
        
        // 7. Process the response
        System.out.println("=== SOAP Response ===");
        response.writeTo(System.out);
        System.out.println("\n");
        
        if (response.getSOAPBody().hasFault()) {
            SOAPFault fault = response.getSOAPBody().getFault();
            String faultCode = fault.getFaultCode();
            String faultString = fault.getFaultString();
            
            System.err.println("SOAP Fault - Code: " + faultCode + ", String: " + faultString);
            
            if ("env:Client".equals(faultCode) && "UnauthenticatedException".equals(faultString)) {
                throw new TracesApiException("UNAUTHENTICATED", "Authentication failed");
            }
            
            // Check for BusinessRulesValidationException in fault detail
            Detail detail = fault.getDetail();
            if (detail != null) {
                // Parse detail for BusinessRulesValidationException
                // This would require additional XML parsing logic
            }
            
            throw new TracesApiException("SOAP_FAULT", faultString);
        }
        
        return response;
    }
    
    /**
     * Custom exception class for TRACES API errors
     */
    public static class TracesApiException extends Exception {
        private final String code;
        
        public TracesApiException(String code, String message) {
            super(message);
            this.code = code;
        }
        
        public String getCode() {
            return code;
        }
    }
    
    /**
     * Example usage
     */
    public static void main(String[] args) {
        try {
            String username = System.getenv("TRACES_ACCEPTANCE_EUDR_USERNAME");
            String authKey = System.getenv("TRACES_ACCEPTANCE_AUTHENTICATION_KEY");
            
            if (username == null || authKey == null) {
                throw new IllegalArgumentException(
                    "TRACES_ACCEPTANCE_EUDR_USERNAME and TRACES_ACCEPTANCE_AUTHENTICATION_KEY must be set");
            }
            
            TracesEudrClient client = new TracesEudrClient(username, authKey);
            
            // You would generate this XML using a service similar to TracesStatementXMLService
            String statementXml = "IMPORT...";
            
            SOAPMessage response = client.submitDDS("OPERATOR", statementXml);
            
            System.out.println("✅ DDS submitted successfully");
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

A modern alternative: Using a REST API gateway (Tanso’s solution)

Given the challenges of the official TRACES API, Tanso provides a modern REST API as an alternative. This allows you to connect your ERP system to the EUDR TRACES system without having to work directly with the TRACES interface. Through a managed API gateway, Tanso’s solution not only converts the complex SOAP calls into lightweight RESTful endpoints, but also offers additional capabilities – such as automated checks of supplier data, validation of EUDR-relevant information, and supporting services to ensure the quality of your DDS submissions.

Example: Using the Tanso EUDR API to create a Due Diligence Statement

Here’s a minimal Java snippet showing how to create a DDS using the Tanso REST API:


import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;


public class TansoEudrClient {

    private final String apiKey;
    private final HttpClient httpClient;

    public TansoEudrClient(String apiKey) {
        this(apiKey, HttpClient.newBuilder()
                .version(Version.HTTP_2)
                .connectTimeout(Duration.ofMillis(TIMEOUT_MS))
                .build());
    }

    public String createDds(String ddsJson) {

        String url = TANSO_EUDR_BASE_URL + EUDR_DDS_PATH;

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.tanso.de/v2/eudr/dds"))
                .header("Content-Type", "application/json")
                .header("X-API-Key", apiKey)
                .POST(HttpRequest.BodyPublishers.ofString(ddsJson))
                .build();


        HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

        String body = response.body();

        return body;
    }


    public static void main(String[] args) {
        try {    
            TansoEudrClient client = new TansoEudrClient(System.getenv("TANSO_EUDR_API_KEY"));

            String ddsJson = """
                {
                  "operatorType": "OPERATOR",
                  "statement": {
                    "activityType": "IMPORT"
                    // ... rest of your DDS data ...
                  }
                }
                """;

            String responseBody = client.createDds(ddsJson);

            System.out.println("✅ DDS created successfully");
            System.out.println("Response from Tanso EUDR API:");
            System.out.println(responseBody);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


With this approach, instead of crafting XML and handling version updates, your ERP (or middleware) can send JSON requests to a well-documented (and stable) REST API, and the gateway will perform the SOAP communication with TRACES behind the scenes. This has several key advantages:

  • Faster Integration: Using a REST API with JSON dramatically cuts development time. Your team can work with familiar JSON payloads and standard HTTP calls, which most modern ERP platforms and integration tools support out-of-the-box.
  • Developer-Friendly Design: Tanso’s EUDR API is designed for ease of use, with clear documentation and consistent RESTful endpoints. Developers don’t need specialized SOAP or XML knowledge. For example, submitting a new Due Diligence Statement might be as simple as an HTTP POST to /api/eudr/v1/dds with a JSON body. The platform handles converting that into the proper TRACES SOAP message.
  • Eliminating Boilerplate: The REST gateway abstracts away authentication complexity and low-level details. It manages the WS-Security, sessions, and error translation. From your perspective, you get straightforward success/failure responses with helpful messages. This frees your developers to focus on business logic rather than plumbing.
  • Enhanced Features: Tanso’s solution includes a dashboard to track submissions, logs of all API calls, and even search functionalities to verify DDS reference numbers in your supply chain. These features help address the evolving needs (like requirement to verify and pass along DDS or declaration IDs from your suppliers).

Using a managed REST API gateway is a smart way to future-proof your EUDR ERP integration. You avoid the pain of direct SOAP handling, save development time, and get a solution that will adapt to API changes for you. Tanso’s solution offers the reliability and scalability of a professionally maintained platform, so your team can focus on automating core ERP and compliance processes instead of writing low-level integration code.

Conclusion

Integrating an ERP system with the EUDR TRACES API is a critical step for companies to ensure compliance with the new deforestation-free supply chain rules. But as we’ve seen, the official TRACES SOAP API presents a range of challenges – from the tedious of XML formats to keeping up with shifting specifications. Direct integration can be a slow, error-prone endeavor, often requiring specialized know-how and significant maintenance as the API evolves.

Fortunately, you don’t have to go it alone. Modern solutions like Tanso’s REST API for EUDR offer a way to cut through the complexity. By leveraging a well-documented, ERP-friendly REST interface on top of the TRACES system, companies can drastically simplify development and ensure their integration remains robust to future changes.

Optimizing your ERP integration strategy now will save time and headaches down the road – and ensure that your business stays in line with EUDR requirements without disrupting operations. Whether you choose to build in-house or use a modern API service, the key is to start early, test thoroughly (especially in the sandbox environment), and design for flexibility.

Entdecken Sie Tanso –
Ihre Komplett­lösung für Nachhaltigkeit

Other articles that may be of interest to you

Stay up-to-date with news from Tanso.