Webhook

L'enregistrement d'un webhook permet de définir un endpoint pour recevoir les notifications des événements envoyés par Uggy.

L'enregistrement d'un webhook permet de définir un endpoint pour recevoir les notifications des événements envoyés par Uggy.


Spécificité du webhook

Votre endpoint doit être de type POST et accepter un format de payload spécifique :

Payload :

{
  event: event_name,
  data: { ... }
}
ℹ️

Le format des data peut varier selon les événements.

Headers:

Chaque requête de webhook contient trois en-têtes significatifs :

En-têteExempleDescription
X-Ifg-Signaturev1=q83K...Signature cryptographique RSA-SHA256 de la requête.
X-Ifg-Timestamp1721568549Timestamp Unix (en secondes) au moment de l'émission.
X-Ifg-Event-Id10243Identifiant unique de l'événement. Peut être null ou absent lors d'événements système (ex: url_validation).

Authentification & Vérification de la signature

La signature vous permet de garantir que la requête provient bien de nos serveurs et n'a pas été altérée en transit.

Pour valider une requête, vous devez utiliser la clé publique (fichier .pem) que vous avez téléchargée depuis la plateforme ifeelgoods

⚠️ Règle d'or : Utilisez le corps brut (raw body)

La signature est générée à partir de la concaténation : <timestamp>.<raw_body>.

Vous devez utiliser le corps brut HTTP exactement tel qu'il a été reçu (sous forme de chaîne de caractères ou de bytes). N'utilisez pas l'objet JSON une fois re-parsé ou ré-encodé, car la moindre différence d'espace ou d'indentation invalidera la vérification.

Exemples d'implémentation


JavaScript (Node.js)

const crypto = require('crypto');

function verifyUggySignature(rawBody, signatureHeader, timestamp, publicKeyPem) {
  if (!rawBody || !signatureHeader || !timestamp) return false;

  try {
    // 1. Extraire la signature sans le préfixe "v1="
    const signature = signatureHeader.replace(/^v1=/, '');

    // 2. Optionnel : Vérifier la tolérance du timestamp (ex: 5 minutes)
    const now = Math.floor(Date.now() / 1000);
    if (Math.abs(now - parseInt(timestamp, 10)) > 300) return false;

    // 3. Reconstituer le payload signé : <timestamp>.<raw_body>
    const payloadToVerify = `${timestamp}.${rawBody}`;

    // 4. Valider la signature en RSA-SHA256
    const verify = crypto.createVerify('RSA-SHA256');
    verify.update(payloadToVerify);

    return verify.verify(publicKeyPem, signature, 'base64url');
  } catch (error) {
    return false;
  }
}

Ruby


require 'openssl'
require 'base64'

def verify_uggy_webhook(raw_body, signature_header, timestamp, public_key_pem_path)
  return false if signature_header.nil? || timestamp.nil?

  # 1. Extraire la signature en retirant le préfixe "v1="
  signature = signature_header.sub(/^v1=/, '')
  signature_bytes = Base64.urlsafe_decode64(signature)

  # 2. Optionnel : vérifier la tolérance du timestamp (ex: 5 minutes)
  max_delay_seconds = 300
  return false if (Time.now.to_i - timestamp.to_i).abs > max_delay_seconds

  # 3. Reconstituer le payload signé <timestamp>.<raw_body>
  payload_to_verify = "#{timestamp}.#{raw_body}"

  # 4. Charger la clé publique PEM et vérifier en RSA-SHA256
  public_key = OpenSSL::PKey::RSA.new(File.read(public_key_pem_path))
  digest = OpenSSL::Digest::SHA256.new

  public_key.verify(digest, signature_bytes, payload_to_verify)
rescue StandardError
  false # Intercepte les erreurs de lecture de clé ou de format malformé
end

PHP

function verifyUggySignature($rawBody,$signatureHeader, $timestamp,$publicKeyPem) {
    if (empty($rawBody) || empty($signatureHeader) \vert{}\vert{} empty($timestamp)) {
        return false;
    }

    // 1. Extraire la signature sans le préfixe "v1="
    $signature = preg_replace('/^v1=/', '',$signatureHeader);
    $signatureBytes = base64_decode(str_replace(['-', '_'], ['+', '/'],$signature));

    // 2. Optionnel : Vérifier la tolérance du timestamp (ex: 5 minutes)
    if (abs(time() - (int)$timestamp) > 300) {
        return false;
    }

    // 3. Reconstituer le payload signé : <timestamp>.<raw_body>
    $payloadToVerify = $timestamp . '.' .$rawBody;

    // 4. Charger la clé publique et valider en RSA-SHA256
    $publicKey = openssl_pkey_get_public($publicKeyPem);
    if (!$publicKey) return false;

    return openssl_verify($payloadToVerify, $signatureBytes,$publicKey, OPENSSL_ALGO_SHA256) === 1;
}

Validation du webhook

Lors de l'enregistrement, un événement spécial est envoyé pour tester et valider votre URL :

{
  event: 'url_validation'
}
ℹ️

L'endpoint doit renvoyer un code de statut entre 200 et 299 pour être validé.

⚠️

Attention sécurité

Pour des raisons de sécurité, restreignez l'IP de votre endpoint et acceptez uniquement les requêtes provenant de Uggy.

13.36.83.200 eu-west-3c
51.44.102.192 eu-west-3b
51.44.87.163 eu-west-3a
52.47.183.210 gateway ifg

Listes des évènements:

Event NameDescriptionData
ifg.product.out_of_stockEnvoyer quand un produit auquel vous avez accès n'est plus disponible
data:{
  product_label: "Nom du produit",
  product_sku: "Sku du produit"
}
ifg.product.back_in_stockEnvoyer quand un produit auquel vous avez accès est de nouveau disponible
data:{
  product_label: "Nom du produit",
  product_sku: "Sku du produit"
}