API
esc

Saisissez votre recherche.

Webhooks

Enregistrez un endpoint HTTPS une seule fois et recevez un événement signé chaque fois qu'une tâche, un import ou un export atteint un état terminal. Réessayé pendant six heures ; dédoublonnez sur l'id.

Enregistrer un endpoint

POST/v1/webhooks
Terminal window
curl https://api.veeton.com/v1/webhooks \
-H "Authorization: Bearer $VEETON_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your.app/veeton",
"events": ["task.completed", "task.failed", "import.completed", "import.failed"],
"description": "Production PIM"
}'
201 Created
{
"id": "01HX5K2MZ7A3Q4FBNDC0EVDXYW",
"url": "https://your.app/veeton",
"events": ["task.completed", "task.failed", "import.completed", "import.failed"],
"status": "active",
"description": "Production PIM",
"last_delivery_at": null,
"created_at": "2026-09-03T14:30:09Z",
"signing_secret": "whsec_2c7f9b41…"
}

L’url doit être un endpoint public en https:// sur le port par défaut, sans identifiants intégrés. Elle est vérifiée ici et devient immuable ensuite ; créez un nouveau webhook pour la modifier. Son nom d’hôte doit aussi résoudre vers des adresses publiques à chaque livraison, et les redirections ne sont pas suivies : indiquez l’URL finale.

Événements

Événement Déclenché lorsque
task.completed, task.failed Une tâche beautifier ou tryon atteint un état terminal
task.quality_checked Expérimental. Le contrôle automatique d’une tâche tryon terminée a un résultat, quelque temps après task.completed. Récupérez la tâche et lisez quality_check.
import.completed, import.failed Un job d’import se termine. Un import dont certaines lignes ont échoué est tout de même completed ; appelez GET /v1/imports/{id} pour le détail ligne par ligne.
export.completed, export.failed Un job d’export se termine. Appelez GET /v1/exports/{id} pour obtenir l’URL signée de l’artefact.

La livraison

Chaque événement est un POST avec un corps JSON et un en-tête HTTP de signature.

Corps de la requête
{
"id": "evt_5f1c3a9e2b7d4e0f8a6c1b2d3e4f5a6b",
"type": "task.completed",
"created_at": "2026-09-03T14:31:40Z",
"data": {
"task": { "id": "01HX5K2MZ7A3Q4FBNDC0EVDXY2", "status": "completed" }
}
}

Le corps nomme la ressource et son statut, rien de plus. Récupérez la ressource pour le reste : l’image de sortie, les compteurs, l’URL de l’artefact. Cela permet de journaliser un payload de webhook sans risque, et garantit qu’un événement arrivant en retard ne transporte jamais de détails obsolètes.

data est indexé par ressource : data.task, data.import ou data.export.

Vérifier la signature

Veeton-Signature: t=1756909900,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

t est l’horodatage Unix auquel la signature a été produite ; v1 est le HMAC-SHA256 en hexadécimal de "{t}.{raw_body}", calculé avec votre signing_secret comme clé. Recalculez-le sur le corps brut de la requête, comparez en temps constant et rejetez les horodatages vieux de plus de quelques minutes.

import { createHmac, timingSafeEqual } from "node:crypto"
export function verify(header: string, rawBody: string, secret: string, toleranceSeconds = 300): boolean {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=") as [string, string]))
const t = Number(parts.t)
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex")
const given = parts.v1 ?? ""
return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected))
}
import hmac, hashlib, time
def verify(header: str, raw_body: bytes, secret: str, tolerance_seconds: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance_seconds:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(parts.get("v1", ""), expected)

Renvoyez un 2xx dès que vous avez accepté l’événement. Faites le travail ensuite : les handlers qui mettent plus de 10 secondes à répondre sont interrompus par timeout et réessayés.

Nouvelles tentatives et doublons

Une livraison qui échoue (réponse non-2xx, redirection 3xx comprise, timeout, hôte injoignable) est réessayée avec un délai croissant d’environ 1 minute, 5 minutes, 15 minutes, 1 heure puis 6 heures, avant d’être abandonnée.

La livraison est at-least-once (au moins une fois). Une nouvelle tentative pour une livraison dont nous n’avons jamais enregistré la réponse est identique à la première. Le champ id (evt_…) est stable d’une tentative à l’autre pour un même événement : traitez-le comme clé d’idempotence de votre côté et ignorez tout id que vous avez déjà traité.

L’interrogation de GET /v1/tasks, GET /v1/imports/{id} et GET /v1/exports/{id} reste toujours disponible pour rattraper le retard après une indisponibilité.

Gérer les endpoints

  • GET/v1/webhooks et GET/v1/webhooks/{id} les relisent, sans le secret.
  • PATCH/v1/webhooks/{id} modifie events, description ou status (active ou paused). Passez rotate_secret: true pour générer un nouveau secret, renvoyé une seule fois dans cette réponse ; l’ancien cesse de fonctionner immédiatement, prévoyez donc une brève interruption si votre récepteur vérifie les signatures de manière synchrone pendant un déploiement.
  • DELETE/v1/webhooks/{id} le supprime.