---
title: "API Commerce : envoyer vos données de boutique à Kanal"
description: "Poussez vos commandes, paniers abandonnés, clients et expéditions vers Kanal depuis n'importe quelle boutique, sans Shopify."
url: https://help.getkanal.com/fr-fr/articles/110-api-commerce-envoyer-vos-donnees-de-boutique-a-kanal
collection: "API"
updated: 2026-10-21
language: fr-fr
alternates: [en]
---

# API Commerce : envoyer vos données de boutique à Kanal

Poussez vos commandes, paniers abandonnés, clients et expéditions vers Kanal depuis n'importe quelle boutique, sans Shopify.

L'API Commerce permet à n'importe quelle boutique d'envoyer ses événements vers Kanal : commandes, paniers abandonnés, clients et expéditions. Ces données alimentent ensuite vos automatisations WhatsApp, sans passer par Shopify.

Contrairement à l'API Messaging, elle ne nécessite **pas d'abonnement actif**. La référence complète est sur [developers.getkanal.com](https://developers.getkanal.com/docs/reference).

> **Info:** **Avant de commencer.** Pour une boutique hors Shopify, créez une boutique personnalisée dans **Paramètres > Boutiques** (cliquez sur le nom de votre équipe, puis sur **Paramètres**). Elle vous donne votre `store_id`, le nombre qui suit `/stores/` dans l'exemple cURL de l'onglet **Guide**, et une clé d'ingestion limitée à cette boutique, affichée en entier une seule fois. Voir [Connecter une boutique hors Shopify (boutique personnalisée)](https://help.getkanal.com/fr-fr/articles/126-connecter-une-boutique-hors-shopify-boutique-personnalisee.md).

Toutes les URL commencent par `https://api.getkanal.com/api/v1/stores/{store_id}/`.

## Le principe : l'upsert

Vous n'avez pas à savoir si un enregistrement existe déjà dans Kanal. Vous envoyez l'objet complet, et Kanal le crée ou le met à jour. Chaque ressource est identifiée par le couple `(store_id, external_id)`.

`external_id` est **votre** identifiant : numéro de commande, jeton de panier, identifiant client. Renvoyez le même `external_id` et Kanal met à jour l'enregistrement existant au lieu de créer un doublon.

-   `201 Created` : Kanal voyait cet `external_id` pour la première fois.
    
-   `200 OK` : un enregistrement existant a été mis à jour.
    

Les deux retournent le même corps JSON : vous pouvez les traiter de la même manière.

> **Info:** **Les rejeux sont sûrs.** Comme les écritures sont indexées sur la clé naturelle, rejouer une requête échouée ou en timeout ne crée jamais de doublon. Attention toutefois : un `POST` rejoué sur une commande ou un panier peut redéclencher les automatisations attachées à cette ressource.

## Commandes

```text
POST /api/v1/stores/{store_id}/orders
```

Déclenche vos automatisations post-achat. Champs **requis** : `external_id` et `total_price`.

Champs optionnels :

-   `placed_at` (format ISO 8601) : sans ce champ, Kanal utilise l'heure de réception de la commande.
    
-   `customer` : l'objet client, avec `phone`, `external_id`, `first_name`, `last_name`, `email`, `locale`, `tags`, `lifetime_value` et `orders_count`.
    
-   `currency`, `financial_status`, `fulfillment_status`, `line_items`, `tags`, `metadata`.
    

> **Warning:** **Sans téléphone, rien n'est déclenché.** Une commande envoyée sans `customer.phone` est bien enregistrée, mais elle ne déclenche aucune automatisation. Envoyez toujours le téléphone du client si la commande doit donner lieu à un message.

```bash
curl -X POST https://api.getkanal.com/api/v1/stores/123/orders \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "ORDER-1001",
    "placed_at": "2026-05-18T10:00:00Z",
    "total_price": 49.90,
    "currency": "EUR",
    "customer": { "phone": "+33612345678", "first_name": "Marie" }
  }'
```

Une commande ne crée pas de fiche client. Pour synchroniser le profil de vos clients (tags, valeur vie, nombre de commandes), envoyez-les aussi via `POST /customers`.

Pour ne modifier que le statut d'une commande existante, utilisez `PATCH /orders/{external_id}`. Seuls `financial_status`, `fulfillment_status`, `tags` et `metadata` sont modifiables. Une commande inexistante retourne une erreur 404.

```bash
curl -X PATCH https://api.getkanal.com/api/v1/stores/123/orders/ORDER-1001 \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{ "financial_status": "paid" }'
```

## Paniers abandonnés

```text
POST /api/v1/stores/{store_id}/checkouts
```

Enregistre vos paniers abandonnés dans Kanal. Champs **requis** : `external_id`, `abandoned_at`, `total_price` et l'objet `customer` avec son `phone`.

Champs optionnels : `recovery_url`, `currency`, `line_items`, `tags`, `metadata`, ainsi que les autres champs de l'objet `customer` (`external_id`, `first_name`, `last_name`, `email`, `locale`, `tags`).

Le champ `recovery_url` est recommandé : c'est le lien qui permet au client de reprendre sa commande.

```bash
curl -X POST https://api.getkanal.com/api/v1/stores/123/checkouts \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "CHECKOUT-7700",
    "abandoned_at": "2026-05-18T09:40:00Z",
    "recovery_url": "https://shop.example.com/checkout/recover/7700",
    "total_price": 49.90,
    "currency": "EUR",
    "customer": { "phone": "+33612345678", "first_name": "Marie" }
  }'
```

> **Warning:** **Relances de panier pas encore disponibles.** Pour une boutique personnalisée, les paniers envoyés sont enregistrés, mais aucun déclencheur de panier abandonné n'est encore proposé dans les automatisations : ils ne déclenchent aucun message pour l'instant.

Le `PATCH /checkouts/{external_id}` permet de faire évoluer le statut du panier, avec les valeurs `active`, `abandoned`, `recovered`, `completed` ou `expired`. Il accepte aussi `recovery_url`, `tags` et `metadata`. Pensez à passer un panier en `recovered` dès qu'il est converti, pour garder des données à jour.

## Clients

```text
POST /api/v1/stores/{store_id}/customers
```

Synchronise les profils clients, leurs tags et leur valeur vie. Seul `phone` est requis. La clé naturelle est `external_id`, mais si vous l'omettez, c'est le `phone` qui sert de clé. Les champs `lifetime_value` et `orders_count` doivent être des nombres entiers.

```bash
curl -X POST https://api.getkanal.com/api/v1/stores/123/customers \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "CUST-42",
    "phone": "+33612345678",
    "first_name": "Marie",
    "tags": ["vip"],
    "lifetime_value": 1299,
    "orders_count": 7
  }'
```

Pour honorer une demande d'effacement RGPD venant de votre boutique, utilisez `DELETE /customers/{external_id}`. La réponse est un `204` sans corps. L'appel est idempotent : supprimer un client déjà supprimé ou inconnu retourne également 204.

## Expéditions

```text
POST /api/v1/stores/{store_id}/shipments
```

Déclenche vos notifications de livraison. Champs **requis** : `external_id` et `order_external_id`, qui rattache l'expédition à la commande correspondante.

Champs optionnels : `tracking_number`, `tracking_url`, `carrier`, `metadata`, et `status` parmi `pending`, `in_transit`, `delivered`, `failed` ou `returned`.

> **Warning:** **Conditions d'envoi.** Aucune notification ne part si l'expédition ne contient pas à la fois `tracking_number` et `tracking_url`, si elle n'a pas de `status`, ou si la commande liée est inconnue de Kanal ou n'a pas de contact. Dans ces cas, l'expédition est quand même enregistrée, sans erreur.

```bash
curl -X POST https://api.getkanal.com/api/v1/stores/123/shipments \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "SHIP-9001",
    "order_external_id": "ORDER-1001",
    "tracking_number": "1Z999AA10123456784",
    "tracking_url": "https://track.example.com/1Z999AA10123456784",
    "carrier": "Colissimo",
    "status": "in_transit"
  }'
```

Il n'existe pas de `PATCH` pour les expéditions : renvoyez un `POST` avec le même `external_id` pour mettre à jour le suivi ou le statut.

## Limites de débit

L'API Commerce est limitée à **500 requêtes par minute et par boutique**. Ce budget est partagé entre tous les endpoints d'une même boutique. En cas de dépassement, l'API retourne un `429` sans en-tête `Retry-After` : le délai d'attente est indiqué dans le corps de la réponse, `retry_after: 60` (en secondes). Des en-têtes `X-RateLimit-*` sont présents sur chaque réponse pour suivre votre consommation.

Trois principes pour rester sous la limite : envoyez un upsert complet plutôt que plusieurs `PATCH` partiels, poussez vos données au moment où elles changent au lieu d'interroger l'API en boucle, et appliquez un backoff exponentiel sur les 429. Si vous prévoyez un volume soutenu supérieur à 500 requêtes par minute, par exemple un import historique ou une vente flash, prévenez votre contact Kanal en amont pour faire relever la limite.

## Gérer les erreurs

Les erreurs prennent deux formes. Les problèmes détectés avant validation (clé, boutique, limite) retournent une simple chaîne `error`. Les payloads invalides retournent un `422` avec un `message` et une carte `errors` détaillée par champ, en notation pointée pour les objets imbriqués. Exemple pour un panier envoyé sans téléphone :

```json
{
  "message": "The customer.phone field is required.",
  "errors": {
    "customer.phone": ["The customer.phone field is required."]
  }
}
```

<table style="min-width: 75px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p><strong>Code</strong></p></th><th colspan="1" rowspan="1"><p><strong>Signification</strong></p></th><th colspan="1" rowspan="1"><p><strong>Rejouer ?</strong></p></th></tr><tr><td colspan="1" rowspan="1"><p><code>200 / 201</code></p></td><td colspan="1" rowspan="1"><p>Mise à jour ou création réussie</p></td><td colspan="1" rowspan="1"><p>Sans objet</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>204</code></p></td><td colspan="1" rowspan="1"><p>Suppression effectuée, sans corps</p></td><td colspan="1" rowspan="1"><p>Sans objet</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>400</code></p></td><td colspan="1" rowspan="1"><p>Paramètre store_id manquant dans l'URL</p></td><td colspan="1" rowspan="1"><p>Non</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>401 / 403</code></p></td><td colspan="1" rowspan="1"><p>Clé absente, invalide ou non autorisée</p></td><td colspan="1" rowspan="1"><p>Non</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>404</code></p></td><td colspan="1" rowspan="1"><p>Boutique inconnue ou en pause, ou enregistrement introuvable</p></td><td colspan="1" rowspan="1"><p>Non</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>422</code></p></td><td colspan="1" rowspan="1"><p>Validation échouée : corrigez le payload</p></td><td colspan="1" rowspan="1"><p>Non</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>429</code></p></td><td colspan="1" rowspan="1"><p>Limite de débit dépassée</p></td><td colspan="1" rowspan="1"><p>Oui, après 60 secondes (<code>retry_after</code>)</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>5xx</code></p></td><td colspan="1" rowspan="1"><p>Erreur serveur passagère</p></td><td colspan="1" rowspan="1"><p>Oui, avec backoff exponentiel</p></td></tr></tbody></table>
