Déclarez un endpoint HTTPS et Pimelo y envoie un payload JSON signé à chaque changement produit — sans polling ni comparaison planifiée.
Les webhooks se déclenchent pour les modifications d'un produit à l'unité depuis l'application, l'API REST ou l'assistant IA. Les opérations en masse — imports CSV/XLSX, éditions groupées, endpoint batch NDJSON, exécutions de règles et synchronisations de connecteurs — ne déclenchent PAS de webhook pour le moment : un import de 50 000 lignes ne peut donc pas saturer votre endpoint.
Après une opération en masse, resynchronisez via l'API : listez les produits filtrés sur updated_at pour récupérer tout ce qui a changé.
Vous enregistrez un endpoint et choisissez les événements qui vous intéressent. Quand l'un d'eux survient, Pimelo envoie une requête HTTP POST contenant une enveloppe JSON décrivant ce qui s'est passé — y compris, pour les mises à jour, le diff exact des valeurs par canal et par locale.
Votre endpoint doit être joignable publiquement en HTTPS et répondre avec un statut 2xx. Toute autre réponse est considérée comme un échec et fait l'objet d'une nouvelle tentative.
Chaque abonnement écoute un ou plusieurs de ces types d'événements.
Chaque livraison est un POST avec un corps JSON et quatre en-têtes Pimelo.
| Header | Description |
|---|---|
| Pimelo-Event-Id | Identifiant unique de l'événement. Utilisez-le comme clé d'idempotence : les nouvelles tentatives et les rejeux manuels réutilisent la même valeur. |
| Pimelo-Event-Type | Le type d'événement, par ex. product.updated. Identique au champ « type » du corps. |
| Pimelo-Delivery-Attempt | Numéro de la tentative pour cette livraison, à partir de 1. |
| Pimelo-Signature | Signature HMAC-SHA256 horodatée du corps de la requête. Voir « Vérifier la signature ». |
{
"id": "evt_3KP7QW2ZR8XN4M",
"type": "product.updated",
"occurredAt": "2026-07-22T09:15:00+00:00",
"organization": "K3M9XQ2P7RTZ8W",
"actor": {
"type": "user",
"uid": "7WQ4MZ2XK9P3TR",
"email": "[email protected]"
},
"source": "app",
"data": {
"uid": "P8ZR3WQ7MK2X4N",
"sku": "TSHIRT-BLUE-M",
"status": "active",
"changes": [
{
"code": "description",
"channelCode": "ecommerce",
"localeCode": "fr_FR",
"oldValue": "Ancien texte",
"newValue": "Nouveau texte"
}
]
}
}
Le tableau « changes » n'est présent que sur product.updated. Un channelCode ou localeCode à null signifie que l'attribut n'est pas scopé par canal ou par locale. Sur product.created et product.deleted, « data » ne contient que l'identité du produit.
Chaque requête est signée avec le secret affiché une seule fois à la création du webhook. Vérifiez-le systématiquement avant de faire confiance à un payload.
# The header carries the signing timestamp and the digest
Pimelo-Signature: t=1753142400,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
Extrayez t et v1 de l'en-tête, rejetez la livraison si t sort de votre fenêtre de tolérance (5 minutes est une bonne valeur par défaut), puis recalculez le HMAC-SHA256 sur la chaîne « {t}.{corpsBrut} » avec votre secret de signature et comparez-le à v1 en temps constant.
function verifyPimeloSignature(
string $rawBody,
string $header,
string $secret,
int $toleranceSeconds = 300
): bool {
// "t=1753142400,v1=9f86d081..."
parse_str(str_replace(',', '&', $header), $parts);
$timestamp = (int) ($parts['t'] ?? 0);
$signature = (string) ($parts['v1'] ?? '');
// Reject stale deliveries (replay protection)
if (abs(time() - $timestamp) > $toleranceSeconds) {
return false;
}
$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
return hash_equals($expected, $signature);
}
const crypto = require('crypto')
function verifyPimeloSignature(rawBody, header, secret, tolerance = 300) {
const parts = Object.fromEntries(
header.split(',').map((p) => p.split('=')),
)
const timestamp = Number(parts.t)
const signature = parts.v1 || ''
// Reject stale deliveries (replay protection)
if (Math.abs(Date.now() / 1000 - timestamp) > tolerance) return false
const expected = crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + rawBody)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
)
}
Important : calculez la signature sur le corps BRUT de la requête, exactement tel que reçu. Parser le JSON puis le re-sérialiser change les octets et la signature ne correspondra jamais.
Ce à quoi vous attendre — et ce pour quoi concevoir votre endpoint.
Toute réponse 2xx. Tout le reste — y compris les redirections 3xx — est un échec et fait l'objet d'une nouvelle tentative.
Jusqu'à 12 tentatives avec backoff exponentiel et jitter : l'attente double à partir de 10 secondes et est plafonnée à une heure, soit une chaîne complète d'environ 3 h 25 après l'événement. Au-delà, la livraison est marquée en échec — et reste rejouable depuis l'historique des livraisons.
Une livraison peut arriver plusieurs fois. Dédupliquez sur Pimelo-Event-Id, qui reste stable entre les tentatives et les rejeux manuels.
Les événements peuvent arriver dans le désordre. Utilisez le champ « occurredAt » pour ignorer les payloads plus anciens que l'état que vous détenez déjà.
Après 20 livraisons en échec consécutives, l'abonnement est désactivé automatiquement afin qu'un endpoint mort cesse de générer du trafic. Réactivez-le depuis la page de réglages.
Les requêtes expirent au bout de 10 secondes. Accusez réception rapidement et traitez en asynchrone — faites le travail lourd après avoir répondu 2xx.
Tout se passe dans l'application, dans Réglages → Système → Webhooks.
Allez dans Réglages → Système → Webhooks et cliquez sur « Créer un webhook ».
Saisissez un libellé et l'URL HTTPS de votre endpoint, puis choisissez les événements à recevoir.
Copiez le secret de signature affiché à la création — il n'est montré qu'une seule fois. Conservez-le avec vos autres secrets.
Utilisez « Tester » pour envoyer une livraison webhook.ping, puis consultez l'historique des livraisons pour inspecter les réponses et rejouer une livraison en échec.
Créez votre compte gratuitement et recevez vos premiers événements produit en quelques minutes.