> ## Documentation Index
> Fetch the complete documentation index at: https://www.zippendo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Canaux personnalisés

> Connectez votre propre boutique en ligne ou votre ERP à Zippendo avec des envois de commandes signés et l'API REST.

Un canal personnalisé permet à votre propre boutique en ligne, à votre ERP ou à toute autre source de commandes que vous contrôlez d'envoyer des commandes dans Zippendo, quand il n'existe pas de connexion prête à l'emploi. Il fonctionne dans les deux sens :

* **Envoyer les commandes** : votre système envoie des événements webhook signés à l'**URL d'ingestion** propre au canal, ou appelle l'[API REST](/docs/api-reference/overview) (SDK officiels disponibles).
* **Recevoir les retours** : abonnez-vous aux [webhooks sortants](/docs/fr/quickstarts/webhooks) de Zippendo (`shipment.dispatched`, `tracking.updated` et d'autres) pour recevoir les numéros de suivi et les changements de statut.

## 1. Créer le canal

Dans [app.zippendo.com](https://app.zippendo.com), cliquez sur **Canaux de vente**, ouvrez l'onglet **Catalogue** et cliquez sur **Créer un canal** sur la carte **Canal personnalisé**. Ou créez-le via l'API :

```bash theme={null}
curl -X POST https://api.zippendo.com/orgs/{orgId}/order-channels \
  -H "Authorization: Bearer zipp_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "My webshop", "type": "custom" }'
```

## 2. Générer le secret de signature

Sur la page des paramètres du canal, cliquez sur **Générer un secret**, ou appelez `POST /orgs/{orgId}/order-channels/{channelId}/webhook-secret`. La réponse contient l'**URL d'ingestion** et le **secret** :

```json theme={null}
{
  "secret": "zwhs_...",
  "webhookUrl": "https://api.zippendo.com/webhooks/order-channels/{channelId}",
  "createdAt": "2026-09-02T12:00:00.000Z"
}
```

<Warning>
  Le secret n'est affiché qu'**une seule fois**. Stockez-le dans la configuration de votre système. Remplacer le secret en crée un nouveau et arrête immédiatement l'ancien. Le révoquer fait rejeter chaque envoi par l'URL d'ingestion jusqu'à ce qu'un nouveau secret existe.
</Warning>

## 3. Envoyer des commandes

Envoyez un `POST` à l'URL d'ingestion avec deux en-têtes :

| En-tête | Valeur |
| - | - |
| `X-Zippendo-Topic` | `order.created`, `order.updated`, `order.cancelled` ou `ping` |
| `X-Zippendo-Signature` | HMAC-SHA256 du **corps brut de la requête**, encodé en hexadécimal, avec votre secret de signature comme clé |

```javascript theme={null}
import crypto from "node:crypto"

const body = JSON.stringify(order)
const signature = crypto.createHmac("sha256", process.env.ZIPPENDO_WEBHOOK_SECRET)
  .update(body)
  .digest("hex")

await fetch(webhookUrl, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Zippendo-Topic": "order.created",
    "X-Zippendo-Signature": signature
  },
  body
})
```

### Contenu d'une commande (`order.created` / `order.updated`)

Les deux sujets se comportent de la même façon : la commande est **créée ou mise à jour** (upsert) sur `externalId`, les renvois et les mises à jour dans le désordre sont donc sans risque. Envoyez celui que votre système trouve le plus simple.

```json theme={null}
{
  "externalId": "SO-10422",
  "orderNumber": "#1042",
  "customerName": "Anna Jensen",
  "customerEmail": "anna@example.dk",
  "shippingAddress": {
    "name": "Anna Jensen",
    "address1": "Nørregade 12",
    "city": "København",
    "postalCode": "1165",
    "countryCode": "DK",
    "phone": "+45 12 34 56 78"
  },
  "orderLines": [
    { "name": "Wool Sweater", "sku": "SKU-1042-BLK", "quantity": 2, "unitPrice": 499.0, "weight": 0.5, "weightUnit": "kg" }
  ],
  "totalAmount": 998.0,
  "currency": "DKK",
  "shippingMethodTitle": "GLS Hjemmelevering"
}
```

| Champ | Obligatoire | Remarques |
| - | - | - |
| `externalId` | ✔ | L'ID de commande de votre système, utilisé comme clé d'idempotence |
| `orderNumber` | ✔ | Numéro de commande lisible |
| `orderLines` | ✔ | Au moins une ligne (`name`, `quantity` ; facultativement `sku`, `unitPrice`, `weight`, …) |
| `shippingAddress` | | Nécessaire pour qu'une expédition soit créée |
| `shippingMethodTitle` | | Comparé aux correspondances de modes de livraison du canal pour choisir une règle d'expédition |
| `serviceCode` | | Code explicite `{carrier}:{product}:{ruleId}[\|{servicePointId}]` ; l'emporte sur la correspondance |
| `customerName`, `customerEmail`, `subtotalAmount`, `totalAmount`, `currency`, `notes` | | Métadonnées facultatives |

### Annulation (`order.cancelled`)

```json theme={null}
{ "externalId": "SO-10422" }
```

### Test de connectivité (`ping`)

Tout corps signé avec `X-Zippendo-Topic: ping` renvoie `200 { "received": true }` sans modifier aucune donnée. Utilisez-le pour vérifier votre code de signature.

### Réponses

| Statut | Signification |
| - | - |
| `200` | Envoi accepté et traité |
| `400` | En-tête de sujet manquant ou inconnu, contenu qui a échoué à la validation, ou canal désactivé (**Canal activé** est désactivé) |
| `401` | Échec de la vérification de la signature (mauvais secret, en-tête manquant, ou aucun secret encore généré) |
| `404` | ID de canal inconnu |
| `429` | [Limite de débit d'ingestion](/docs/api-reference/rate-limits) atteinte. Réessayez après le délai `Retry-After` |
| `5xx` | Échec du traitement. Renvoyez l'envoi : l'upsert rend le renvoi sans risque |

## Ce qui arrive à une commande envoyée

1. La commande est créée ou mise à jour (upsert) dans le canal (en héritant de la [marque](/docs/fr/settings/brands) du canal, le cas échéant).
2. Une règle d'expédition est déterminée : le `serviceCode` explicite, sinon les **correspondances de modes de livraison** du canal comparées à `shippingMethodTitle`.
3. Une expédition est créée. Les expéditions avec une règle correspondante sont prêtes à être envoyées. Avec seulement une **Adresse de l'expéditeur par défaut** définie, un brouillon est créé à la place.
4. Si **Envoyer les expéditions automatiquement** est activé pour le canal, une expédition avec une règle correspondante est envoyée immédiatement au transporteur, et l'étiquette d'expédition arrive dans Zippendo (l'impression automatique de la règle s'applique).

## Alternative REST

Si vous préférez requête/réponse aux webhooks, créez les commandes via l'API avec un [jeton d'API](/docs/fr/settings/api-settings) :

```bash theme={null}
curl -X POST https://api.zippendo.com/orgs/{orgId}/orders \
  -H "Authorization: Bearer zipp_..." \
  -H "Content-Type: application/json" \
  -d '{
    "orderChannelId": "{channelId}",
    "orderNumber": "#1042",
    "externalId": "SO-10422",
    "orderLines": [{ "name": "Wool Sweater", "quantity": 2 }],
    "shippingAddress": { "name": "Anna Jensen", "address1": "Nørregade 12", "city": "København", "postalCode": "1165", "countryCode": "DK" },
    "shippingMethodTitle": "GLS Hjemmelevering"
  }'
```

* Un `externalId` en double sur le même canal renvoie **`409 ORDER_EXISTS`**. Traitez-le comme « déjà reçu ».
* Passer `shippingRuleId` ou `shippingMethodTitle` (ou activer **Envoyer les expéditions automatiquement** pour le canal) crée l'expédition dans le même appel, exactement comme un envoi.
* Les canaux de vente font partie de l'API publique, les [SDK](/docs/fr/sdks/overview) couvrent donc chaque étape : créer le canal, générer le secret, envoyer des commandes et lister les expéditions.

## Récupérer le suivi

Zippendo prévient votre système via les [webhooks sortants](/docs/fr/quickstarts/webhooks) : abonnez-vous à `shipment.dispatched` et `tracking.updated` dans **Paramètres → Webhooks**. Les expéditions portent votre numéro de commande comme `reference`, et le contenu inclut l'`orderId` pour les recherches.

## Étapes suivantes

<CardGroup cols={2}>
  <Card title="Webhooks sortants" icon="webhook" href="/docs/fr/quickstarts/webhooks">
    Recevez les événements de suivi et de statut, et vérifiez nos signatures.
  </Card>

  <Card title="Référence de l'API" icon="terminal" href="/docs/api-reference/overview">
    Chaque point de terminaison de canal de vente et de commande, avec des exemples.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.