Integrare come piattaforma
Aggiungi MoovExpress come corriere dentro la tua piattaforma, e lascia che siano i tuoi utenti a collegare il proprio account
Se gestisci una piattaforma — un gestionale di spedizioni, un e-commerce SaaS, un marketplace — puoi offrire MoovExpress ai tuoi utenti come corriere disponibile. Scrivi il connettore una volta sola; poi ogni tuo utente collega il proprio account MoovExpress incollando la sua API key.
Il modello in una riga
Tu costruisci il connettore. Il tuo utente incolla la sua API key MoovExpress (una per corriere). Tu, con quella chiave, chiami la nostra API per conto suo.
Nessuna chiave condivisa, nessun account "di piattaforma": ogni chiamata avviene con la chiave del singolo utente, e vede solo i dati del suo contratto.
Tre cose da sapere prima di iniziare
- Una chiave = un contratto = un corriere. Un utente che vuole spedire con Poste e con BRT inserisce due chiavi, una per ciascun corriere. Progetta il campo "API key" per corriere collegato, non uno solo per account.
- La chiave la genera l'utente, dal proprio portale MoovExpress in Impostazioni → API Key. Tu la ricevi già pronta: non serve alcun onboarding lato tuo.
- Isolamento totale. Ogni chiave è legata a un solo contratto: l'utente A non può vedere o toccare le spedizioni dell'utente B. È il database a garantirlo, non il tuo codice.
Conserva le API key dei tuoi utenti cifrate e mai in codice lato browser. Valgono come una password: permettono di creare spedizioni e addebitare il credito del contratto collegato.
Il flusso di integrazione
1. Chiedi la API key (una per corriere)
Nel punto in cui l'utente aggiunge un corriere — es. "Collega Poste di MoovExpress" — mostra un campo per incollare la chiave (mvx_live_…).
2. Valida e identifica la connessione
Appena ricevi la chiave, chiama GET /api/v1/account: è il test di connessione e ti dice quale corriere è quella chiave, così etichetti la connessione senza chiederlo all'utente.
curl https://moovexpress.com/api/v1/account \
-H "Authorization: Bearer mvx_live_xxxxxxxxxxxxxxxxxxxxxxxx"
const res = await fetch("https://moovexpress.com/api/v1/account", {
headers: { Authorization: `Bearer ${chiaveUtente}` },
});
if (res.status === 401) throw new Error("Chiave non valida");
const acc = await res.json();
// acc.contratto → es. "Poste" : usalo come etichetta della connessione
// acc.contratto_attivo, acc.listino_attivo, acc.credito → mostra lo stato all'utente
{ "ok": true, "account": "Cliente Demo Srl", "contratto": "Standard Nazionale",
"contratto_attivo": true, "listino_attivo": true, "credito": 150.00, "valuta": "EUR" }
3. Le operazioni, per conto dell'utente
Da qui in poi usi sempre la chiave di quell'utente. Ogni chiamata è isolata sul suo contratto.
| Passo | Endpoint | A cosa serve |
|---|---|---|
| Preventivo | POST /api/v1/rates | Prezzo del corriere per quel pacco e destinazione |
| Crea spedizione | POST /api/v1/shipments | Genera LDV + tracking + label_url + prezzo |
| Etichetta | GET /api/v1/shipments/{id}/label | Scarica il PDF dell'etichetta |
| Tracking | GET /api/v1/tracking/{tracking} | Stato ed eventi (meglio i webhook) |
| Ritiro | POST /api/v1/pickups | Prenota il ritiro delle spedizioni |
| Distinta | POST /api/v1/shipping-lists | Chiude la distinta di viaggio |
| Annulla | DELETE /api/v1/shipments/{id} | Annulla una spedizione (reversibile 48h) |
Dettaglio di ogni endpoint, con parametri e risposte, nel riferimento API.
Come si chiede il ritiro. Chiedilo sempre alla creazione, con il campo pickup nel corpo di POST /shipments (data: un giorno lavorativo, da oggi in poi):
{ "packages": [ ... ], "shipFrom": { ... }, "shipTo": { ... },
"pickup": { "requested": true, "date": "2026-08-12", "time": "mattina" } }
Poi leggi il blocco ritiro della risposta:
"richiesto": true→ il ritiro è prenotato presso il corriere. Ilcodicespesso arriva qualche minuto dopo: se ènull, rileggilo conGET /shipments/{id}. Su questi contratti il ritiro non si può aggiungere dopo, quindi non chiamarePOST /pickups(risponde400)."richiesto": false→ su questo contratto il ritiro non si prenota insieme alla spedizione. Il campoavvisodice come richiederlo: di norma conPOST /pickups.
4. Ricevi gli aggiornamenti di stato
Per il tracking, preferisci i webhook: li registra il singolo utente (o li registri tu per suo conto) e ti notifichiamo noi appena una spedizione cambia stato — così non interroghi /tracking in continuazione.
Esempio completo, dalla tariffa alla consegna
Un flusso reale end-to-end con la chiave di un tuo utente: preventivo → creazione → etichetta, poi la ricezione degli aggiornamenti via webhook. È in Node.js, ma la sequenza e i campi sono identici in qualunque linguaggio.
Preventivo, spedizione ed etichetta
const BASE = "https://moovexpress.com/api/v1";
// Piccolo wrapper: usa SEMPRE la chiave del singolo utente (una per corriere).
async function moov(path, { key, method = "GET", body } = {}) {
const res = await fetch(BASE + path, {
method,
headers: {
Authorization: `Bearer ${key}`,
...(body ? { "Content-Type": "application/json" } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
if (res.status === 429) {
const attesa = Number(res.headers.get("Retry-After") || 5);
throw new Error(`Limite di frequenza: riprova tra ${attesa}s`);
}
const data = await res.json();
if (!res.ok) throw new Error(data.error || `Errore ${res.status}`); // 401, 402 credito, 400…
return data;
}
async function creaSpedizione(key) {
// 0) Verifica la chiave e scopri QUALE corriere è (per etichettare la connessione).
const acc = await moov("/account", { key });
console.log(`Connesso a ${acc.contratto} — credito € ${acc.credito}`);
const packages = [{ weight: 2.5, length: 30, width: 20, height: 15 }]; // kg, cm
const shipFrom = {
name: "Magazzino del merchant", street1: "Via Torino 5", city: "Milano",
state: "MI", postalCode: "20100", country: "IT",
phone: "0212345678", email: "logistica@merchant.it",
};
const shipTo = {
name: "Mario Rossi", street1: "Via Roma 10", city: "Roma",
state: "RM", postalCode: "00100", country: "IT",
phone: "3331234567", email: "mario.rossi@example.com",
};
// 1) Preventivo (facoltativo: per mostrare il prezzo prima di confermare).
const rate = await moov("/rates", { key, method: "POST", body: { packages, shipTo } });
console.log(`Prezzo previsto: € ${rate.prezzo} (${rate.contratto})`);
// 2) Crea la spedizione: torna id, tracking, prezzo effettivo e il link all'etichetta.
const sped = await moov("/shipments", {
key, method: "POST",
body: { packages, shipFrom, shipTo, contenuto: "Abbigliamento", notes: "Citofonare Rossi" },
});
console.log(`Creata ${sped.tracking} — € ${sped.prezzo} — stato ${sped.stato}`);
// 3) Scarica l'etichetta (PDF). label_url è già pronto nella risposta.
const pdfRes = await fetch(BASE + `/shipments/${sped.id}/label`, {
headers: { Authorization: `Bearer ${key}` },
});
const etichettaPdf = Buffer.from(await pdfRes.arrayBuffer()); // salvala o inviala in stampa
return { id: sped.id, tracking: sped.tracking, etichettaPdf };
}
Ricevere gli aggiornamenti (endpoint webhook)
Il tuo utente registra l'URL del tuo webhook dal suo portale MoovExpress e riceve un secret (whsec_…): conservalo per quel merchant e usalo per verificare la firma di ogni evento.
import express from "express";
import crypto from "crypto";
const app = express();
// Serve il CORPO GREZZO: la firma si calcola su di esso, prima di ogni parsing.
app.use("/webhook/moov", express.raw({ type: "application/json" }));
app.post("/webhook/moov", (req, res) => {
const raw = req.body; // Buffer grezzo
const parti = Object.fromEntries(
(req.get("Webhook-Signature") || "").split(",").map((p) => p.split("="))
);
const t = Number(parti.t);
// 1) Anti-replay (scarta oltre 5 minuti) + firma HMAC-SHA256 timing-safe.
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return res.sendStatus(401);
const atteso = crypto
.createHmac("sha256", secretDelMerchant) // il whsec_ di QUESTO merchant
.update(`${t}.${raw.toString("utf8")}`)
.digest("hex");
const a = Buffer.from(atteso), b = Buffer.from(parti.v1 || "");
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401);
// 2) Rispondi SUBITO 200, poi elabora in background (eviti timeout e retry).
res.sendStatus(200);
const { event, data } = JSON.parse(raw.toString("utf8"));
switch (event) {
case "tracking.updated": /* aggiorna lo stato dell'ordine del merchant */ break;
case "tracking.delivered": /* segna "consegnato": data.tracking_number, data.location */ break;
case "tracking.exception": /* anomalia: avvisa il merchant */ break;
}
});
Il label_url nella risposta della creazione è già l'endpoint dell'etichetta di quella spedizione: puoi usarlo così com'è (prefissato con l'host) invece di ricomporre l'URL a mano.
Cosa NON vedrai (ed è voluto)
I corrieri effettivi e il trasporto dietro le quinte ti arrivano già con nomi neutri (il nome del contratto, es. "Poste", "Standard Nazionale"). I fornitori tecnici a monte non compaiono mai nelle risposte: né a te, né ai tuoi utenti.
Buone pratiche
- Rispetta i limiti di frequenza. Se ricevi
429, attendi i secondi indicati inRetry-Afterprima di riprovare. Vedi Autenticazione → Limiti di frequenza. - Non spezzare in loop il tracking: usa i webhook o intervalli ragionevoli.
- Gestisci il
402: se il credito dell'utente non basta, la creazione risponde402— mostraglielo e invitalo a ricaricare dal suo portale. - Una chiave revocata smette di funzionare all'istante: previeni un messaggio chiaro all'utente quando
/accountrisponde401.
Parti sempre da GET /api/v1/account: è gratis, non consuma quota, e ti conferma in un colpo che la chiave è valida, quale corriere rappresenta e quanto credito ha l'utente.