Développeurs

Connectez votre propre boutique à Shiplayer.

Votre boutique ne tourne ni sur Shopify, ni sur WooCommerce, ni sur Magento ? Nos connecteurs n’utilisent rien que vous ne puissiez utiliser : une API REST, une clé, cinq appels. Ce guide est le chemin le plus court, avec les requêtes à copier.

1La commandeVotre système l’envoie dès qu’elle est passée.POST /orders
2Les tarifsChaque transporteur, son prix et son coût réel attendu.POST /orders/{id}/quotes
3L’expéditionUn transporteur, ou « auto ». Le colis existe.POST /orders/{id}/ship
4L’étiquetteUn PDF à imprimer. Le chauffeur passe.GET /shipments/{ref}/label.pdf
5Le suiviPoussé chez vous par webhook, ou lu quand vous voulez.GET /shipments/{ref}
Authentification

Une clé, un en-tête.

Le marchand crée sa clé lui-même, dans son tableau de bord : Paramètres → Clés API → Créer une clé. Elle s’affiche une seule fois. Une clé « shp_live_… » crée de vrais colis ; une clé « shp_test_… » sert à développer. Envoyez-la dans l’en-tête Authorization de chaque requête.

Base URL     https://shiplayer.online
Content-Type application/json
Authorization: Bearer shp_live_xxxxxxxxxxxxxxxxxxxxxxxx

# Vérifiez la clé : votre compte marchand
curl https://shiplayer.online/merchants/me \
  -H "Authorization: Bearer $SHIPLAYER_KEY"

Toutes les réponses sont en JSON. Les montants sont des chaînes en dirhams (« 38.08 »), jamais des flottants. Les dates sont en UTC, format ISO 8601. La limite est d’environ 10 requêtes par seconde par clé : au-delà, 429, réessayez après une seconde.

La référence complète de chaque route, générée depuis le code : shiplayer.online/docs.

Étape 1

Créer la commande.

Envoyez la commande dès qu’elle est passée dans votre boutique. Elle arrive dans la boîte « Commandes » du marchand, avec son score de risque et le lien WhatsApp pour la confirmer avec le client — l’expédition se décide ensuite, par vous ou par lui.

curl -X POST https://shiplayer.online/orders \
  -H "Authorization: Bearer $SHIPLAYER_KEY" -H "Content-Type: application/json" \
  -d '{
    "order_id": "CMD-10482",
    "origin": {
      "raw": "12 rue Ibnou Mounir, Maarif", "city": "Casablanca",
      "phone": "0661000000", "recipient_name": "Boutique Atlas"
    },
    "destination": {
      "raw": "Résidence Yasmine, imm. B, apt 4, Guéliz", "city": "Marrakech",
      "phone": "0662000000", "recipient_name": "Salma Bennani"
    },
    "parcel": { "weight_kg": "0.8" },
    "payment_mode": "cod",
    "cod_amount_mad": "450",
    "declared_value_mad": "450",
    "ready_at": "2026-10-14T09:00:00",
    "contents": "Caftan",
    "notes": "Appeler avant livraison"
  }'

201 Created
{
  "order_id": "CMD-10482",
  "recipient": "Salma Bennani", "city": "Marrakech", "zone": "metro",
  "cod_mad": "450", "needs_address_review": false,
  "risk": { "score": 34, "band": "medium", "action": "confirm_first", ... },
  "whatsapp_url": "https://wa.me/212662000000?text=...",
  ...
}
ChampObligatoireNotes
order_idouiVotre numéro de commande, 64 caractères max. Il doit être unique : le renvoyer répond 409, ce qui rend vos réessais sans danger.
origin, destinationouiraw (l’adresse en texte libre), city, phone, recipient_name. Pas de zone à fournir : nous la déduisons de la ville. La ville doit être orthographiée comme les transporteurs la connaissent (« Missour », pas « Mayssour »).
parcel.weight_kgouiSupérieur à 0. Certains transporteurs ne prennent pas les colis trop légers ou trop lourds sur certaines villes : ils n’apparaissent alors pas dans les tarifs.
payment_modeouicod (contre-remboursement, alors cod_amount_mad est obligatoire) ou prepaid.
declared_value_madouiLa valeur de la marchandise, base de l’indemnisation et de l’assurance.
ready_atouiQuand le colis sera prêt à être ramassé.
contents, notesnonLe contenu est imprimé sur l’étiquette ; la remarque est transmise au transporteur.
exchange_contentsnonUn échange : ce que le livreur reprend chez le client pendant la même visite.
Étape 2

Comparer les tarifs.

Facultatif, si vous laissez Shiplayer choisir. Sinon, cet appel donne chaque transporteur qui dessert la ville, avec son prix et son coût réel attendu — le prix plus le risque d’échec et la trésorerie immobilisée, la méthode expliquée sur la page d’accueil. Les transporteurs qui ne desservent pas la ville sont listés dans not_offered, avec la raison.

curl -X POST https://shiplayer.online/orders/CMD-10482/quotes \
  -H "Authorization: Bearer $SHIPLAYER_KEY"

200 OK
{
  "recommended": { "carrier_code": "relaisexpress", "price_mad": "30.00", ... },
  "candidates": [
    { "carrier_code": "relaisexpress", "price_mad": "30.00",
      "expected_landed_cost_mad": "48.10", "promised_transit_hours": 48,
      "rate_basis": "contract", "insurance_offer_fee_mad": "6.75", ... },
    { "carrier_code": "sendit", "price_mad": "45.00", ... }
  ],
  "not_offered": [
    { "carrier_code": "digylog", "reason": "Digylog n'accepte pas les colis de moins de 1.2 kg." }
  ]
}
Étape 3

Expédier.

Nommez un transporteur, ou laissez « auto » : Shiplayer prend celui au coût réel le plus bas selon le réglage du marchand. La réponse est le colis complet — gardez shiplayer_ref, c’est la référence de tout le reste, et carrier_tracking_ref, le numéro du transporteur. Le lien de suivi à donner au client est shiplayer.online/t/{shiplayer_ref}.

curl -X POST https://shiplayer.online/orders/CMD-10482/ship \
  -H "Authorization: Bearer $SHIPLAYER_KEY" -H "Content-Type: application/json" \
  -d '{ "carrier_code": "auto", "insured": false }'

200 OK
{
  "shiplayer_ref": "SHP8B064DE84C28",
  "order_id": "CMD-10482",
  "status": "created",
  "total_price_mad": "30.00",
  "legs": [ { "carrier_code": "relaisexpress", "carrier_tracking_ref": "RE123456789",
              "from_city": "Casablanca", "to_city": "Marrakech", "price_mad": "30.00",
              "promised_transit_hours": 48, ... } ],
  "events": [ { "at": "2026-10-13T14:18:44Z", "status": "created",
                "description": "Expédition créée, étiquette prête" } ],
  "pickup": { "stated": true, "date": "2026-10-14", "when": "demain avant 12 h", ... },
  "cancellable": true,
  ...
}
En un seul appel. Si votre système décide tout seul, POST /shipments prend la commande (order) et le transporteur (carrier_code) dans le même corps et crée le colis directement. Envoyez un en-tête Idempotency-Key unique par commande : un réessai après un délai dépassé renvoie le colis déjà créé au lieu d’en acheter un second.
Compte validé. Créer une commande et demander les tarifs marche dès l’inscription. Expédier demande un compte validé par Shiplayer (dossier et contrat) : avant, la réponse est 409 et son message dit ce qui manque.
Étape 4

L’étiquette et le ramassage.

L’étiquette est un PDF au format du transporteur, avec le logo et le contenu. Imprimez-la et collez-la sur le colis. Le ramassage se gère dans le tableau de bord du marchand (Expéditions → Ramassages) : il y voit l’heure du passage et confirme la remise au chauffeur. Pour une annulation, c’est possible tant que le colis n’a pas été ramassé.

# L'étiquette (PDF)
curl https://shiplayer.online/shipments/SHP8B064DE84C28/label.pdf \
  -H "Authorization: Bearer $SHIPLAYER_KEY" -o etiquette.pdf

# Plusieurs étiquettes en un PDF
curl "https://shiplayer.online/labels.pdf?ref=SHP8B064DE84C28&ref=SHP2F19C0A7E415" \
  -H "Authorization: Bearer $SHIPLAYER_KEY" -o etiquettes.pdf

# Annuler avant ramassage
curl -X POST https://shiplayer.online/shipments/SHP8B064DE84C28/cancel \
  -H "Authorization: Bearer $SHIPLAYER_KEY"
Étape 5

Suivre le colis.

Le meilleur moyen est le webhook ci-dessous : chaque changement arrive chez vous, sans rien demander. Sinon, lisez le colis quand vous voulez ; la réponse est la même que celle de l’expédition, avec l’historique complet dans events. La page publique /t/{ref} et son JSON /track/{ref} n’ont pas besoin de clé et ne montrent rien de confidentiel : vous pouvez les donner au client.

curl https://shiplayer.online/shipments/SHP8B064DE84C28 \
  -H "Authorization: Bearer $SHIPLAYER_KEY"

200 OK
{
  "shiplayer_ref": "SHP8B064DE84C28", "status": "delivered",
  "delivered_at": "2026-10-16T11:02:00Z",
  "events": [
    { "at": "2026-10-13T14:18:44Z", "status": "created",          "description": "Expédition créée, étiquette prête" },
    { "at": "2026-10-14T10:40:12Z", "status": "carrier_received", "description": "Pris en charge", "location": "Casablanca" },
    { "at": "2026-10-15T07:12:30Z", "status": "in_transit",       "location": "Marrakech" },
    { "at": "2026-10-16T08:05:00Z", "status": "out_for_delivery" },
    { "at": "2026-10-16T11:02:00Z", "status": "delivered" }
  ],
  "pod": { "recipient_name": "Salma Bennani", "recorded_at": "2026-10-16T11:02:00Z", ... },
  ...
}

# Sans clé, pour le client
curl https://shiplayer.online/track/SHP8B064DE84C28
Webhooks

Soyez prévenu, ne demandez pas.

Le marchand indique l’adresse de votre système dans Paramètres → Webhook et reçoit un secret, affiché une fois. À partir de là, Shiplayer envoie un POST JSON à cette adresse à chaque création de colis et à chaque changement de statut. Le corps est une enveloppe dont data est exactement l’objet de GET /shipments/{ref}, plus tracking_url : vous n’avez qu’un seul format à connaître.

ÉvénementQuand
shipment.createdLe colis vient d’être créé : il a sa référence, son transporteur et son étiquette.
shipment.updatedLe statut du colis a changé (pris en charge, en transit, livré, échec, retourné, annulé…). Une fois par changement, pas à chaque scan.
pingLe bouton « Tester » du tableau de bord. Répondez 200 et c’est tout.
POST https://ma-boutique.ma/webhooks/shiplayer
Content-Type: application/json
User-Agent: Shiplayer-Webhooks/1.0
Shiplayer-Event: shipment.updated
Shiplayer-Delivery: whd_3f9c2a7b1e4d5a6c8b0f
Shiplayer-Signature: t=1760364004,v1=5c1a0f…e2b9

{
  "id": "whd_3f9c2a7b1e4d5a6c8b0f",
  "event": "shipment.updated",
  "created_at": "2026-10-16T11:02:04Z",
  "merchant_id": "mrc_atlas",
  "data": {
    "shiplayer_ref": "SHP8B064DE84C28",
    "order_id": "CMD-10482",
    "status": "delivered",
    "tracking_url": "https://shiplayer.online/t/SHP8B064DE84C28",
    "legs": [ ... ], "events": [ ... ], "pod": { ... },
    ...
  }
}

Vérifier la signature

Chaque envoi est signé avec le secret du marchand : v1 = HMAC-SHA256(secret, "{t}.{body}"), en hexadécimal. Recalculez-la sur le corps tel que reçu, octet pour octet, avant de le parser ; comparez en temps constant ; refusez un t à plus de cinq minutes de votre horloge. Un envoi dont la signature ne correspond pas ne vient pas de Shiplayer.

import hmac, hashlib, time

def verify(secret: str, header: str, body: bytes) -> bool:
    fields = dict(p.split("=", 1) for p in header.split(","))
    t, v1 = int(fields["t"]), fields["v1"]
    if abs(time.time() - t) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

# Flask
@app.post("/webhooks/shiplayer")
def shiplayer():
    if not verify(SECRET, request.headers["Shiplayer-Signature"], request.get_data()):
        abort(401)
    msg = request.get_json()
    if msg["event"] == "shipment.updated":
        update_order(msg["data"]["order_id"], msg["data"]["status"], msg["data"]["tracking_url"])
    return "", 200

Fiabilité

Répondez 2xx en moins de dix secondes, puis traitez. Toute autre réponse, ou l’absence de réponse, est réessayée : après 1, 5 et 15 minutes, puis 1, 3, 6, 12 et 24 heures — huit tentatives sur un jour. Un même message peut donc arriver deux fois : gardez son id et ignorez un doublon. L’ordre n’est pas garanti entre deux messages proches : fiez-vous au statut et aux events de data, pas à l’ordre d’arrivée. Le marchand voit chaque envoi, sa réponse et son prochain essai dans Paramètres → Webhook → Journal des envois.

L’adresse doit être en https, sur un serveur public. Pendant le développement, un tunnel (ngrok, Cloudflare Tunnel) vers votre machine fait l’affaire ; le bouton « Tester » envoie un ping tout de suite et affiche la réponse de votre serveur.

Statuts

Un seul vocabulaire, pour tous les transporteurs.

Chaque transporteur a ses propres codes ; Shiplayer les traduit tous dans cette liste. Le code d’origine reste dans raw_carrier_code de chaque event si vous en avez besoin.

statusSens
createdColis créé, étiquette prête, en attente de ramassage.
picked_upLe marchand a confirmé la remise au chauffeur.
carrier_receivedLe transporteur a scanné le colis : il est dans son réseau.
in_transitEn route vers la ville de destination.
out_for_deliveryChez le livreur, livraison dans la journée.
attempt_failedTentative échouée (absent, injoignable, refus). Le marchand décide dans son tableau de bord : nouvelle tentative ou retour.
deliveredLivré. Final. Le contre-remboursement est dû par le transporteur ; pod porte la preuve quand il la fournit.
returnedRetourné au marchand. Final.
cancelledAnnulé avant ramassage : rien n’a quitté la boutique. Final.
lostPerdu, déclaré par le transporteur. Final ; l’indemnisation s’applique.
Erreurs

Ce que les codes veulent dire.

Le corps d’une erreur est toujours un objet avec une clé detail : une phrase en français, ou pour 422 la liste des champs refusés avec leur emplacement.

401Clé absente, inconnue ou révoquée.
404La commande ou le colis n’existe pas — ou appartient à un autre marchand, ce qui est la même réponse.
409Conflit : order_id déjà utilisé, commande déjà expédiée, compte pas encore validé, Idempotency-Key réutilisée avec un autre corps. Le message dit lequel.
422Corps invalide : champ manquant, trop long, montant négatif, ville non desservie par aucun transporteur.
429Trop de requêtes : attendez une seconde et réessayez.
5xxShiplayer ou le transporteur n’a pas répondu. Rien n’a été créé : réessayez, avec le même Idempotency-Key si vous passez par POST /shipments.
Une question, un cas qui n’est pas ici ? Écrivez-nous avec le shiplayer_ref ou l’order_id concerné : contact@shiplayer.online. La référence complète : /docs.