colisdocs

Webhooks & n8n

Un événement signé à chaque étape d’un colis : envoyé, ouvert, validé, à corriger, supprimé. Le vérifier, et le brancher sur n8n.

Chaque étape de la vie d’un colis peut lancer une automatisation : vous prévenir qu’il a été ouvert, déplacer une carte, envoyer la facture une fois la livraison validée, faire lire le document par une IA. La page de livraison publie ces événements ; n’importe quelle route qui accepte un POST peut les recevoir.

Les activer

Sur le déploiement de la page de livraison, deux variables :

DROP_WEBHOOK_URL=https://n8n.example.com/webhook/colis,https://votre.app/api/colis
DROP_WEBHOOK_SECRET=…   # 32 caractères ou plus : openssl rand -base64 32

DROP_WEBHOOK_URL prend une adresse ou plusieurs séparées par des virgules. Le secret est obligatoire dès qu’une adresse est définie, et doit faire au moins 32 caractères : sinon le déploiement refuse de démarrer. Redéployez après avoir changé l’une ou l’autre.

Les événements

ÉvénementQuand
parcel.sentUn colis a été créé, par POST /api/transfers ou par un envoi de gros fichier terminé.
parcel.openedLe client l’a ouvert : la page de retrait, un téléchargement ou une réponse. La première fois seulement.
parcel.approvedLe client a validé la livraison.
parcel.changes_requestedLe client a demandé des corrections ; data.comment dit lesquelles.
parcel.revisedL’expéditeur a répondu par une nouvelle version, sous le même code et le même lien.
parcel.deletedLe code a été détruit : DELETE, ou le téléchargement d’un colis à usage unique.

L’expéditeur qui ouvre son propre lien ne déclenche pas parcel.opened : il est reconnu par son jeton d’expéditeur (voir Suivi et validation). Un colis qui expire simplement n’envoie rien.

Le contenu

Chaque événement est un POST en JSON, envoyé après la réponse, donc personne ne l’attend :

{
  "id": "5f0c6f7e-…",
  "type": "parcel.approved",
  "createdAt": "2026-09-22T12:00:00.000Z",
  "data": {
    "code": "K7QP2M4X",
    "url": "https://votre-livraison.vercel.app/K7QP2M4X",
    "filename": "maquette-v2.pdf",
    "size": 1843200,
    "contentType": "application/pdf",
    "expiresAt": "2026-09-29T10:00:00.000Z",
    "note": "La version avec le logo corrigé",
    "device": "Chrome sur macOS",
    "protected": false,
    "oneTime": false,
    "status": "approved",
    "sentAt": "2026-09-22T10:00:00.000Z",
    "openedAt": "2026-09-22T11:00:00.000Z",
    "decidedAt": "2026-09-22T12:00:00.000Z",
    "version": 1
  }
}

data décrit le colis au moment où l’événement part ; status vaut sent, opened, approved ou changes. data.version dit de quelle version il s’agit : 1 pour le premier fichier, puis une de plus à chaque parcel.revised. Un workflow sur .changes_requested et .revised suit ainsi tous les allers-retours. data.url est la page de retrait, construite depuis DROP_URL ou, à défaut, le domaine de production Vercel. data ne contient jamais le mot de passe, son empreinte ni le jeton de l’expéditeur. Le message (note) disparaît des événements avec le colis ; le nom, la taille et le type sont gardés avec l’accusé, donc une validation arrivée après un téléchargement unique nomme encore le fichier.

Les types TypeScript sont exportés par @colis/protocol : ColisWebhookEvent, un type par événement (ColisParcelApprovedEvent…), ColisParcelData, et la liste COLIS_WEBHOOK_EVENT_TYPES avec isColisWebhookEventType().

La signature

Les événements sont signés selon Standard Webhooks :

POST https://votre.app/api/colis
content-type: application/json
webhook-id: 5f0c6f7e-…
webhook-timestamp: 1790078400
webhook-signature: v1,<base64 du HMAC-SHA256 de "${id}.${timestamp}.${body}">

La clé est le secret en UTF-8, ou ses octets décodés pour un secret écrit whsec_<base64>, que les bibliothèques officielles vérifient alors telles quelles. Vérifiez avec la fonction qui signe :

app/api/colis/route.ts
import { verifyWebhook, type ColisWebhookEvent } from '@colis/protocol'

export async function POST(request: Request) {
  const body = await request.text() // le corps brut, avant tout JSON.parse
  const result = await verifyWebhook(process.env.DROP_WEBHOOK_SECRET!, request.headers, body)
  if (!result.valid) return new Response(null, { status: 401 }) // result.reason dit pourquoi

  const event = JSON.parse(body) as ColisWebhookEvent
  if (event.type === 'parcel.changes_requested') {
    // event.data.comment : ce que le client a écrit
  }

  return new Response(null, { status: 204 })
}

result.reason vaut missing-headers, invalid-timestamp, stale-timestamp ou invalid-signature. Un horodatage décalé de plus de cinq minutes (WEBHOOK_TOLERANCE_SECONDS) est refusé, pour qu’une ancienne livraison ne puisse pas être rejouée ; toleranceSeconds en option change cette marge. signWebhook() et webhookHeaders() produisent la même signature, pour tester votre récepteur.

Les nouvelles tentatives

Chaque événement est tenté trois fois au plus, à 0,5 s puis 2 s d’intervalle, cinq secondes chacune. Une réponse 4xx autre que 408 et 429 n’est pas retentée, et une redirection n’est pas suivie. Un récepteur qui reste en panne rate l’événement : il est journalisé avec son origine, jamais son chemin ni le secret, et aucun envoi ni téléchargement n’échoue à cause de lui.

Dédupliquez sur webhook-id : une nouvelle tentative porte le même. Et comme rien n’est rejoué après la troisième, gardez colis statut pour vérifier.

Avec n8n

packages/n8n-nodes-colis fournit deux nœuds et un type d’identifiants :

NœudRôle
Colis TriggerLance le workflow sur parcel.sent, .opened, .approved, .changes_requested, .revised ou .deleted.
ColisSur un colis : Send un fichier binaire, Get ses métadonnées, Get Status de sa livraison, Delete.

L’installer

Dans n8n : Settings → Community nodes → Install, tapez n8n-nodes-colis et confirmez. Les deux nœuds et les identifiants Colis API apparaissent ensuite dans le panneau des nœuds.

Sur une instance auto-hébergée sans cet écran, lancez npm install n8n-nodes-colis dans ~/.n8n/nodes, puis redémarrez n8n.

Le brancher

  1. Identifiants. Créez des identifiants Colis API :
    • Base URL : le déploiement, par exemple https://votre-livraison.vercel.app.
    • Upload Password : le DROP_PASSWORD du déploiement, s’il en a un. Seul Send s’en sert.
    • Webhook Secret : le DROP_WEBHOOK_SECRET du déploiement. Seul le déclencheur s’en sert.
  2. Le déclencheur. Ajoutez un Colis Trigger, choisissez les événements (tous par défaut) et copiez sa Production URL.
  3. Le déploiement. La page de livraison n’a pas d’API d’abonnement : collez cette adresse dans DROP_WEBHOOK_URL, mettez le même secret dans DROP_WEBHOOK_SECRET, redéployez, puis activez le workflow.

Avec un secret dans les identifiants, le déclencheur vérifie chaque signature avec verifyWebhook et répond 401 à toute requête non signée, falsifiée ou vieille de plus de cinq minutes. Un événement que vous n’avez pas coché reçoit 200 et ne lance rien, pour que le déploiement ne le retente pas. Sans secret, tout est pris sur parole : gardez-en un.

Le nœud Colis

OpérationRequêteRenvoie
SendPOST /api/transfers, le fichier en corpscode, size, expiresAt, et url : la page de retrait
GetGET /api/transfers/:codeNom, taille, type, expiration, message, oneTime, protected
Get StatusGET /api/transfers/:code/statussent, opened, approved ou changes, avec les heures
DeleteDELETE /api/transfers/:code{ code, deleted: true }

Send prend le fichier dans un champ binaire (data par défaut) et les options de la page : durée de vie, message, mot de passe, destruction au premier téléchargement, nom du fichier, étiquette d’appareil (n8n par défaut). Il envoie en une seule requête : le fichier doit tenir sous DROP_MAX_SIZE_MB. Pour un colis protégé, remplissez Parcel Password sur les trois autres opérations.

Deux workflows complets sont décrits dans les cas d’usage : facturer dès la validation et un résumé IA de chaque livrable.

Sur cette page