I am integrating Mondial Relay’s SOAP API in a Node.js application using the soap npm package.
I am trying to create a shipment with the WSI2_CreationExpedition method and then generate the shipping label with WSI2_CreationEtiquette.
My code generates the SOAP XML request, which looks well-formed when I log it, but the server consistently responds with a 500 error:
**> Invalid XML Error: Unexpected close tag Line: 158, Column: 19,
Char: >**
I properly clean empty fields from the request object to avoid empty XML tags.
I calculate the security key (MD5) according to the exact field order required.
I use the official WSDL URL: https://api.mondialrelay.com/Web_Services.asmx?WSDL
I have verified:
The XML request logged is well-formed.
The namespaces and SOAP versions match the documentation.
The authentication (brand ID and private key) is correct.
The relay codes used exist and are active.
What could cause the "Unexpected close tag" error despite valid XML? Are there any quirks or encoding issues with Node.js soap library when integrating with Mondial Relay?
I would appreciate help with debugging steps or examples of working Node.js calls with Mondial Relay SOAP API.
import soap from "soap";
import { createHash } from "crypto";
// URL du WSDL officiel Mondial Relay
const wsdlUrl = "https://api.mondialrelay.com/Web_Services.asmx?WSDL";
const BRAND_ID = "xxxx";
const PRIVATE_KEY = "xxxx";
// Supprime les champs vides dans l'objet pour éviter XML invalide
function removeEmptyFields(obj) {
return Object.fromEntries(
Object.entries(obj).filter(
([_, v]) => v !== "" && v !== undefined && v !== null
)
);
}
// Calcule la clé sécurité MD5 selon l'ordre des champs
function calculateSecurity(args, key, fields) {
const concatenated = fields.map((field) => args[field] || "").join("") + key;
return createHash("md5")
.update(concatenated, "utf8")
.digest("hex")
.toUpperCase();
}
// Champs attendus pour la création d'expédition (ordre important)
const creationFields = [
"Enseigne",
"ModeCol",
"ModeLiv",
"NDossier",
"NClient",
"Expe_Langage",
"Expe_Ad1",
"Expe_Ad2",
"Expe_Ad3",
"Expe_Ad4",
"Expe_Ville",
"Expe_CP",
"Expe_Pays",
"Expe_Tel1",
"Expe_Tel2",
"Expe_Mail",
"Dest_Langage",
"Dest_Ad1",
"Dest_Ad2",
"Dest_Ad3",
"Dest_Ad4",
"Dest_Ville",
"Dest_CP",
"Dest_Pays",
"Dest_Tel1",
"Dest_Tel2",
"Dest_Mail",
"Poids",
"Longueur",
"Taille",
"NbColis",
"CRT_Valeur",
"CRT_Devise",
"Exp_Valeur",
"Exp_Devise",
"COL_Rel_Pays",
"COL_Rel",
"LIV_Rel_Pays",
"LIV_Rel",
"TAvisage",
"TReprise",
"Montage",
"TRDV",
"Assurance",
"Instructions",
"Texte",
];
// Champs attendus pour la création d'étiquette (ordre important)
const labelFields = ["Enseigne", "ExpeditionNum"];
async function createFullShipment() {
try {
const client = await soap.createClientAsync(wsdlUrl);
// Place ici le listener pour récupérer la requête SOAP brute
client.on("request", (xml) => {
console.log("Requête SOAP brute:\n", xml);
});
// Prépare données sans champs vides
let cleanShipmentData = removeEmptyFields({
Enseigne: BRAND_ID,
ModeCol: "REL",
ModeLiv: "24R",
NDossier: "",
NClient: "",
Expe_Langage: "FR",
Expe_Ad1: "74 rue de l'ancienne poste",
Expe_Ad2: "DUPONT",
Expe_Ad3: "DUPONT",
Expe_Ad4: "DUPONT",
Expe_Ville: "Franqueville saint pierre",
Expe_CP: "76520",
Expe_Pays: "FR",
Expe_Tel1: "+33643281434",
Expe_Tel2: "",
Expe_Mail: "",
Dest_Langage: "FR",
Dest_Ad1: "12 rue des fleurs",
Dest_Ad2: "DUPONT",
Dest_Ad3: "DUPONTDUPONT",
Dest_Ad4: "DUPONT",
Dest_Ville: "Rouen",
Dest_CP: "76000",
Dest_Pays: "FR",
Dest_Tel1: "+33612345678",
Dest_Tel2: "",
Dest_Mail: "",
Poids: "1000",
Longueur: "10",
Taille: "10",
NbColis: "1",
CRT_Valeur: "0",
CRT_Devise: "",
Exp_Valeur: "0",
Exp_Devise: "",
COL_Rel_Pays: "FR",
COL_Rel: "32765",
LIV_Rel_Pays: "FR",
LIV_Rel: "20359",
TAvisage: "N",
TReprise: "N",
Montage: "N",
TRDV: "N",
Assurance: "0",
Instructions: "",
Texte: "",
});
// Complète cleanShipmentData pour clé sécurité
creationFields.forEach((field) => {
if (!cleanShipmentData.hasOwnProperty(field)) {
cleanShipmentData[field] = "";
}
});
// Calcul clé sécurité
cleanShipmentData.Security = calculateSecurity(
cleanShipmentData,
PRIVATE_KEY,
creationFields
);
// Création expédition
const [creationResult] = await client.WSI2_CreationExpeditionAsync(
cleanShipmentData
);
/* if (creationResult.STAT !== "0") {
throw new Error(
`Erreur création expédition, code: ${creationResult.STAT}`
);
}*/
console.log("Expédition créée, numéro :", creationResult.ExpeditionsNum);
// Préparation et calcul sécurité pour création étiquette
let etiquetteArgs = {
Enseigne: BRAND_ID,
ExpeditionNum: creationResult.ExpeditionsNum,
};
etiquetteArgs.Security = calculateSecurity(
etiquetteArgs,
PRIVATE_KEY,
labelFields
);
// Création étiquette
const [etiquetteResult] = await client.WSI2_CreationEtiquetteAsync(
etiquetteArgs
);
if (!etiquetteResult || !etiquetteResult.ListeEtiquettes) {
throw new Error("Erreur création étiquette");
}
console.log("Etiquette créée :", etiquetteResult.ListeEtiquettes);
return { creationResult, etiquetteResult };
} catch (error) {
//console.error("Erreur process complet :", error);
throw error;
}
}
// Appel sécurisé avec gestion promesses
createFullShipment()
.then((result) => {
console.log("Process terminé avec succès :", result);
})
.catch((error) => {
console.error("Erreur attrapée dans appel principal :", error);
});