Aller au contenu principal
Produits imprimés dès 1 pièce

Webhooks : recevoir automatiquement les mises à jour de commande

12 min de lectureMis à jour

Avec les webhooks, Printen.eu vous envoie un message dès qu’il se passe quelque chose sur une commande : en attente de solde, en production, expédiée avec suivi, livrée ou annulée. Vous n’avez donc plus à interroger l’API en permanence. Dans cet article, vous ajoutez un endpoint, vérifiez la signature et construisez un traitement qui supporte les répétitions.

Dans cet article

Comment fonctionnent les webhooks ?

Vous nous donnez une URL https de votre propre serveur. À chaque événement, nous y envoyons une requête POST avec un message JSON. Votre serveur répond avec un statut 2xx, et nous savons alors que le message est arrivé.

De l’événement au traitement.
  • X-Printen-Event : le type d’événement, par exemple order.shipped.
  • X-Printen-Delivery : un id de cette livraison. Pratique dans vos journaux.
  • X-Printen-Timestamp : l’heure d’envoi, en secondes depuis 1970 (temps Unix).
  • X-Printen-Signature : la signature (v1= suivi d’un code hexadécimal), qui vous permet de vérifier que le message vient bien de nous.

Comment ajouter un endpoint ?

L’onglet Webhooks de votre tableau de bord.
  1. Ouvrez l’onglet

    Dans votre tableau de bord, allez dans Webhooks.

  2. Saisissez votre URL

    Dans URL (https), saisissez l’adresse de votre propre serveur, par exemple https://votredomaine.fr/printen-webhook.

  3. Choisissez les événements

    Cochez les événements que vous voulez recevoir. Par défaut, order.shipped, order.payment_failed et order.awaiting_funds sont cochés.

  4. Ajoutez le webhook

    Cliquez sur Ajouter un webhook. Le secret s’affiche, avec le message Conservez ce secret maintenant. Il ne s’affichera qu’une fois.

  5. Conservez le secret

    Placez aussitôt le secret dans la configuration de votre serveur, par exemple dans une variable d’environnement. Vous en avez besoin pour vérifier la signature.

Vous pouvez aussi ajouter un endpoint via l’API avec POST /webhooks. Si vous omettez events ou envoyez une liste vide, vous recevez tous les événements. Le secret ne figure que dans cette réponse.

Ajouter un endpoint via l’API
curl -X POST https://www.printen.eu/api/reseller/v1/webhooks \
  -H "Authorization: Bearer prk_live_..." -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/printen-webhook","events":["order.shipped","order.delivered"]}'
Réponse
{
  "data": {
    "id": "WEBHOOK_ID",
    "url": "https://example.com/printen-webhook",
    "events": ["order.shipped", "order.delivered"],
    "allEvents": false,
    "active": true,
    "createdAt": "2026-10-10T09:00:00.000Z",
    "updatedAt": "2026-10-10T09:00:00.000Z",
    "secret": "WEBHOOK_SECRET"
  }
}
  • L’URL doit utiliser https et être accessible publiquement. Les adresses comme localhost, les adresses de réseau interne et les noms se terminant par .local, .internal ou .lan sont refusés.
  • Vous pouvez avoir au maximum 10 endpoints par compte.
  • Chaque endpoint a son propre secret.

Quels événements existent ?

ÉvénementQuandCe que vous faites en général
order.createdUne commande a été créée, via l’API, le tableau de bord ou une boutique connectée.Enregistrer notre numéro de commande avec votre propre commande.
order.submittedLa commande est payée et part en production.Passer la commande à « en cours » dans votre système.
order.awaiting_fundsVotre solde est insuffisant ; la commande attend.Vous alerter et recharger.
order.payment_failedLe paiement avec votre carte enregistrée a échoué.Vérifier votre carte et réessayer la commande dans le tableau de bord.
order.in_productionLa production a commencé.Afficher le statut. L’annulation n’est plus possible à partir de maintenant.
order.shippedLe colis est expédié ; tracking contient le numéro et le lien.Envoyer le suivi à votre client ou l’ajouter dans votre boutique.
order.deliveredLe colis a été livré.Clôturer la commande.
order.cancelledLa commande a été annulée.Annuler la commande dans votre système et traiter un éventuel remboursement.
order.file_problemIl y a un problème avec le fichier d’impression. Nous vous contactons.Vérifier votre fichier et répondre à notre message.

À quoi ressemble un message ?

Chaque message a la même structure. Voici un exemple de order.shipped.

Exemple : order.shipped
{
  "id": "EVENT_ID",
  "type": "order.shipped",
  "created": "2026-10-10T14:32:05.000Z",
  "data": {
    "order": {
      "id": "ORDER_ID",
      "orderNumber": "RS-3F9A1C7B2E",
      "externalOrderId": "1001",
      "externalOrderNumber": "1001",
      "state": "SUBMITTED",
      "status": "SHIPPED",
      "tracking": {
        "code": "3SABCD1234567",
        "url": "https://example.com/track/3SABCD1234567"
      },
      "items": [
        {
          "productId": "PRODUCT_ID",
          "name": "Poster",
          "quantity": 1,
          "options": { "size": "A2" }
        }
      ]
    }
  }
}
ChampSignification
idId unique de l’événement. Traitez-le comme un texte sans structure fixe et utilisez-le pour reconnaître les doublons.
typeLe type d’événement, identique à l’en-tête X-Printen-Event.
createdLe moment où l’événement s’est produit (ISO 8601).
data.order.orderNumberNotre numéro de commande, tel qu’affiché dans le tableau de bord.
data.order.externalOrderIdVotre propre id de commande, si vous l’avez envoyé lors de la commande.
data.order.state et statusLe statut de votre côté (par exemple SUBMITTED) et le statut de production (par exemple SHIPPED). Voir les statuts de commande.
data.order.trackingNuméro et lien de suivi, dès que la commande est expédiée.
Champs supplémentaires dans dataPour order.awaiting_funds et order.payment_failed, la raison figure dans reason. order.cancelled peut contenir refundedCents. Dans le bac à sable, order.submitted contient un champ test.

Comment vérifier la signature ?

N’importe qui peut envoyer une requête à votre URL. Vérifiez donc la signature de chaque message avant d’agir. La signature est un HMAC-SHA256 calculé avec le secret de votre endpoint.

  1. Lisez les en-têtes

    Récupérez X-Printen-Timestamp et X-Printen-Signature dans la requête.

  2. Vérifiez l’heure

    Rejetez le message si l’horodatage s’écarte de plus de 5 minutes (300 secondes) de l’heure actuelle. Personne ne peut ainsi rejouer un ancien message.

  3. Calculez la signature attendue

    Calculez un HMAC-SHA256 avec votre secret sur le texte timestamp.rawBody : l’horodatage, un point puis le corps brut. Ajoutez v1= devant.

  4. Comparez en sécurité

    Comparez le résultat à X-Printen-Signature avec une comparaison à temps constant. S’ils ne correspondent pas, répondez 401 et ne traitez rien.

Node.js
import crypto from 'node:crypto';

export function verifyPrintenWebhook(rawBody, headers, secret) {
  const timestamp = headers['x-printen-timestamp'];
  const signature = headers['x-printen-signature'] || '';
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = 'v1=' + crypto.createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`).digest('hex');
  return signature.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
PHP
<?php
function verify_printen_webhook(string $rawBody, string $timestamp, string $signature, string $secret): bool {
    if (abs(time() - (int) $timestamp) > 300) return false;
    $expected = 'v1=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
    return hash_equals($expected, $signature);
}
Python
import hashlib, hmac, time

def verify_printen_webhook(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False
    expected = "v1=" + hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

Piège : vérifiez sur le corps brut

La signature est calculée sur le corps exactement tel que nous l’envoyons, octet par octet. Beaucoup de frameworks lisent automatiquement le corps comme du JSON. Si vous reconvertissez ensuite ce JSON en texte, les espaces, l’ordre ou certains caractères peuvent différer légèrement, et la signature ne correspond plus.

Express : conserver le corps brut
import express from 'express';
import { verifyPrintenWebhook } from './verify-printen-webhook.js';

const app = express();

// express.raw (not express.json) keeps the body exactly as it was sent
app.post('/printen-webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const rawBody = req.body.toString('utf8');
  if (!verifyPrintenWebhook(rawBody, req.headers, process.env.PRINTEN_WEBHOOK_SECRET)) {
    return res.status(401).send('invalid signature');
  }
  const event = JSON.parse(rawBody);
  await saveEventForProcessing(event); // store first, process later
  res.status(200).send('ok');
});
  • PHP : lisez le corps avec file_get_contents("php://input"), pas avec $_POST.
  • Python (Flask) : utilisez request.get_data() pour les octets bruts, pas request.json.
  • Next.js (App Router) : utilisez await request.text() et seulement ensuite JSON.parse.

Et si votre endpoint ne répond pas ?

Une livraison réussit si vous répondez en moins de 10 secondes avec un statut 2xx. Tout autre résultat compte comme un échec : un délai dépassé, un statut d’erreur, mais aussi une redirection (3xx). Nous réessayons alors selon ce calendrier.

TentativeAttente après la tentative précédenteEnviron après la première tentative
1ImmédiatementDépart
21 minute1 minute
35 minutes6 minutes
415 minutes21 minutes
530 minutes51 minutes
61 heure2 heures
72 heures4 heures
84 heures8 heures
98 heures16 heures
108 heures24 heures

Après la dixième tentative, nous abandonnons pour cet événement. Votre endpoint n’est jamais désactivé automatiquement : nous continuons d’envoyer les nouveaux événements.

Traiter les événements en double ou dans le désordre

À cause des répétitions, un même événement peut arriver plusieurs fois, et les événements peuvent arriver dans un autre ordre que celui où ils se sont produits. Concevez votre traitement en conséquence.

  • Dédoublonnez sur `id` : enregistrez l’id de chaque événement traité. Si le même id revient, répondez 2xx et ne faites rien d’autre.
  • Surveillez l’ordre avec `created` : si vous avez déjà traité un événement plus récent pour cette commande, n’écrasez pas un statut récent par un plus ancien.
  • Ne faites jamais reculer un statut : si order.shipped arrive après order.delivered, laissez la commande au statut livrée.
  • En cas de doute, consultez la commande : GET /orders/{id} vous donne toujours l’état actuel.

Comment tester votre webhook ?

  1. Préparez votre endpoint

    Assurez-vous que votre URL est accessible publiquement en https. Si vous travaillez en local, utilisez un service de tunnel qui redirige une URL https publique vers votre ordinateur.

  2. Ajoutez l’endpoint

    Ajoutez-le dans Webhooks et cochez order.created, order.submitted et order.cancelled (ou omettez les événements via l’API pour tout recevoir).

  3. Passez une commande de test

    Passez une commande via l’API avec une clé de test (prk_test_). En quelques minutes, vous recevez order.created et order.submitted (avec test dans data).

  4. Annulez la commande de test

    Annulez la commande avec POST /orders/{id}/cancel. Vous recevez alors order.cancelled.

  5. Vérifiez vos journaux

    Vérifiez que la signature était correcte, que vous avez répondu 2xx et qu’un message répété avec le même id a bien été ignoré.

Résoudre les problèmes

Je ne reçois rien du tout.

Vérifiez que l’URL est correcte, utilise https et est accessible depuis internet (pas localhost ni une adresse interne). Vérifiez que vous avez coché les bons événements. Gardez à l’esprit qu’un événement peut mettre quelques minutes à arriver.

La signature ne correspond jamais.

Vérifiez que vous utilisez le secret de cet endpoint (chaque endpoint a le sien) et que vous vérifiez sur le corps brut, pas sur du JSON reconstruit. Voir le corps brut.

Je reçois des messages en double.

C’est normal après un délai dépassé ou un statut d’erreur. Dédoublonnez sur l’id de l’événement. Voir les doublons.

Mon serveur renvoie une redirection.

Nous ne suivons pas les redirections ; un 301 ou 302 compte comme un échec. Utilisez l’URL finale, en faisant attention au www, à la barre oblique finale et au passage de http à https.

Je n’ai pas conservé le secret.

Supprimez l’endpoint et ajoutez-le à nouveau. Vous obtenez un nouveau secret ; mettez-le à jour dans la configuration de votre serveur.

Je ne peux plus ajouter d’endpoint.

Vous avez atteint le maximum de 10 endpoints. Supprimez d’abord un endpoint que vous n’utilisez plus.

Besoin d’aide ?

Nous vous aidons volontiers, en personne les jours ouvrés et généralement le jour même.

Tous les articles