Webhooks clients

ONESSTA envoie une requête HTTP POST à votre URL de callback lorsqu'un statut de colis change. Une modification de la situation de paiement seule déclenche aussi un webhook. Il s'agit d'une notification sortante d'ONESSTA vers votre serveur, et non d'un endpoint de l'API à appeler.

Configuration

  1. Dans votre compte onessta.com, ouvrez Profil → Webhook Config.
  2. Renseignez votre Webhook URL (une URL HTTPS publique) et une Secret Key que vous conservez côté serveur.
  3. Enregistrez la configuration. ONESSTA envoie les événements uniquement aux endpoints actifs liés au compte du client.
  4. Utilisez Tester le Webhook dans le profil : un événement webhook.test est envoyé sans modifier de colis.

La API-Key utilisée pour appeler l'API ONESSTA n'est pas envoyée dans les webhooks clients. Utilisez X-Signature et votre Secret Key pour authentifier la notification.

Événements

  • parcel.status_updated : changement du statut principal status ou secondaire status_second.
  • parcel.situation_updated : changement de situation sans changement de statut, par exemple NOT_PAID → PAID.
  • webhook.test : test manuel sans colis ; data.test vaut true.

Si le statut et la situation changent dans la même mise à jour, un seul événement parcel.status_updated est envoyé ; le champ data.parcel.situation contient déjà la nouvelle valeur. Une modification des seules coordonnées du destinataire ne déclenche pas actuellement un webhook client. La création d'un colis ne déclenche pas cet événement de changement de statut.

Requête envoyée

POST https://votre-domaine.example/webhooks/onessta
Content-Type: application/json
User-Agent: Onessta-Webhook/1.0
X-Webhook-Event: parcel.status_updated
X-Webhook-Delivery: {event_id UUID}
X-Signature: {HMAC-SHA256 du payload avec votre Secret Key}

La structure est event_id (UUID stable lors des retries), event_type, timestamp, puis data.parcel et data.additional_data. Tous les événements utilisent les mêmes champs data.parcel ; seules les valeurs de additional_data dépendent du changement.

Champs du colis

  • code : code du colis chez ONESSTA.
  • status : statut envoyé au client. Si le statut principal interne vaut IN_PROGRESS, ce champ reprend status_second (et peut donc être null).
  • status_second : statut secondaire du colis, ou null.
  • situation : situation de paiement du colis, par exemple NOT_PAID, INVOICED ou PAID.
  • comment : dernier commentaire de statut (last_statut_comment dans ONESSTA), éventuellement null.
  • receiver, phone, address : coordonnées du destinataire.
  • city_id, city_name, district : localisation du destinataire ; certains champs peuvent être null.
  • total_fees : frais du colis, avec 0 par défaut si la valeur est absente.
  • reported_date : date reportée au format YYYY-MM-DD HH:MM:SS, ou null.
  • created_at, updated_at : dates ISO 8601, ou null.

data.additional_data.delivery_man contient les informations du livreur si elles sont disponibles (sinon un tableau vide). data.additional_data.reported_date reprend aussi la date reportée. Le webhook client n'envoie pas de champ external_id ni de header X-API-Key.

Exemple : changement du statut principal

{
  "event_id": "8d46bb4c-9fae-48bd-a5ac-48b267093df4",
  "event_type": "parcel.status_updated",
  "timestamp": "2026-10-20T09:00:00+00:00",
  "data": {
    "parcel": {
      "code": "ABC123",
      "status": "DELIVERED",
      "status_second": null,
      "situation": "NOT_PAID",
      "comment": "Livré avec succès",
      "receiver": "Client Exemple",
      "phone": "0600000000",
      "address": "Casablanca",
      "city_id": 97,
      "city_name": "Casablanca",
      "district": null,
      "total_fees": 50,
      "reported_date": null,
      "created_at": "2026-10-18T08:00:00+00:00",
      "updated_at": "2026-10-20T09:00:00+00:00"
    },
    "additional_data": {
      "old_status": "DISTRIBUTION",
      "new_status": "DELIVERED",
      "status_second": null,
      "delivery_man": [],
      "reported_date": null
    }
  }
}

Exemple : changement du statut secondaire

L'event_type reste parcel.status_updated. Dans cet exemple, status dans data.parcel vaut PROGRAMED parce que le statut principal interne est IN_PROGRESS ; le statut principal reste visible dans additional_data.status.

{
  "event_id": "eeea9616-d650-4165-94aa-58ce6bd794aa",
  "event_type": "parcel.status_updated",
  "timestamp": "2026-10-20T09:30:00+00:00",
  "data": {
    "parcel": {
      "code": "ABC123",
      "status": "PROGRAMED",
      "status_second": "PROGRAMED",
      "situation": "NOT_PAID",
      "comment": null,
      "receiver": "Client Exemple",
      "phone": "0600000000",
      "address": "Casablanca",
      "city_id": 97,
      "city_name": "Casablanca",
      "district": null,
      "total_fees": 50,
      "reported_date": null,
      "created_at": "2026-10-18T08:00:00+00:00",
      "updated_at": "2026-10-20T09:30:00+00:00"
    },
    "additional_data": {
      "status": "IN_PROGRESS",
      "old_status_second": null,
      "new_status_second": "PROGRAMED",
      "delivery_man": [],
      "reported_date": null
    }
  }
}

Exemple : changement de situation

{
  "event_id": "f51aa46a-f952-40c1-8be8-359ae80b3063",
  "event_type": "parcel.situation_updated",
  "timestamp": "2026-10-20T10:30:00+00:00",
  "data": {
    "parcel": {
      "code": "ABC123",
      "status": "DELIVERED",
      "status_second": null,
      "situation": "PAID",
      "comment": null,
      "receiver": "Client Exemple",
      "phone": "0600000000",
      "address": "Casablanca",
      "city_id": 97,
      "city_name": "Casablanca",
      "district": null,
      "total_fees": 50,
      "reported_date": null,
      "created_at": "2026-10-18T08:00:00+00:00",
      "updated_at": "2026-10-20T10:30:00+00:00"
    },
    "additional_data": {
      "old_situation": "NOT_PAID",
      "new_situation": "PAID",
      "delivery_man": [],
      "reported_date": null
    }
  }
}

Les exemples montrent les trois cas de notification. Les valeurs du colis et les dates sont illustratives ; les champs optionnels peuvent être vides ou null.

Signature, réponse et erreurs

X-Signature est un HMAC-SHA256 hexadécimal calculé avec votre Secret Key sur les octets exacts du corps HTTP reçu. Ne réencodez pas le JSON avant la vérification. Comparez la signature en temps constant, puis parsez le JSON. Les webhooks clients n'envoient pas de X-API-Key ; ne les confondez pas avec les webhooks partenaires.

Exemple PHP

$rawBody = file_get_contents('php://input');
$expected = hash_hmac('sha256', $rawBody, $secretKey);
$received = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
if (!hash_equals($expected, $received)) {
    http_response_code(401);
    exit;
}
$payload = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
// Dédupliquez par event_id avant de traiter le colis.
http_response_code(200);

Exemple Node.js (Express)

import crypto from 'node:crypto';
import express from 'express';

const app = express();
app.post('/webhooks/onessta', express.raw({ type: 'application/json' }), (req, res) => {
  const received = req.get('X-Signature') ?? '';
  const expected = crypto.createHmac('sha256', process.env.ONESSTA_WEBHOOK_SECRET)
    .update(req.body).digest('hex');
  const valid = /^[a-f0-9]{64}$/i.test(received) &&
    crypto.timingSafeEqual(Buffer.from(received, 'hex'), Buffer.from(expected, 'hex'));
  if (!valid) return res.sendStatus(401);
  const event = JSON.parse(req.body.toString('utf8'));
  // Dédupliquez par event.event_id et traitez en arrière-plan.
  return res.sendStatus(200);
});

Répondez rapidement avec un code 2xx et traitez l'événement de manière asynchrone. Le délai maximal de la requête est de 60 secondes (connexion : 5 secondes). Toute erreur réseau ou réponse HTTP non-2xx est réessayée automatiquement par la queue, jusqu'à 3 tentatives au total (délais de 10 puis 30 secondes). Le profil client affiche les 20 derniers envois, leurs statuts, le code HTTP et le nombre d'essais. Un envoi échoué peut être relancé manuellement avec le même event_id. Une erreur 401 indique généralement une signature ou une Secret Key incorrecte : corrigez la configuration avant de relancer. Les retries peuvent produire des doublons ; utilisez event_id comme clé d'idempotence.