Vérification des signatures de webhook

Vérifiez les événements qu'Affirm envoie aux points de terminaison de votre webhook.

Affirm signe les événements webhook qu'il envoie à vos points de terminaison. Pour ce faire, nous incluons une signature dans l'en-tête "Affirm-Signature" de chaque événement. Cela vous permet de vérifier que les événements ont été envoyés par Affirm, et non par un tiers. Vous pouvez vérifier les signatures manuellement en utilisant votre propre solution.

🚧

Une signature webhook ne peut être configurée que par Affirm. Veuillez communiquer avec Affirm via le widget d'assistance.

Empêcher les attaques en replay

Une attaque par replay est lorsqu'un attaquant intercepte une charge utile valide et sa signature, puis les retransmet. Pour atténuer de telles attaques, Affirm inclut un horodatage dans l'en-tête Affirm-Signature. Étant donné que cet horodatage fait partie de la charge utile signée, il est également vérifié par la signature, donc un attaquant ne peut pas modifier l’horodatage sans invalider la signature. Si la signature est valide mais que l’horodatage est trop ancien, vous pouvez demander à votre application de rejeter la charge utile.

❗️

Tolérance d'horodatage

Nous vous recommandons de définir une tolérance par défaut (par exemple cinq minutes) entre l'horodatage et l'heure actuelle, et d'utiliser le protocole NTP (Network Time Protocol), afin de vous assurer que l'horloge de votre serveur est précise et se synchronise avec l'heure des serveurs d'Affirm.

Affirm génère l'horodatage et la signature chaque fois que nous envoyons un événement à votre point de terminaison. Si Affirm tente de relancer un événement (par exemple, votre point de terminaison a précédemment répondu avec un code de statut non-2xx), nous générons une nouvelle signature et un nouvel horodatage pour la nouvelle tentative de livraison.

Vérification manuelle des signatures

L'en-tête X-Affirm-Signature contient un horodatage et une ou plusieurs signatures. L'horodatage est préfixé par t=, et chaque signature est préfixée par un schéma. Les schémas commencent par v, suivi d'un entier. Actuellement, le seul schéma de signature valide est v0.

x-affirm-signature:
t=1582267948,
v0=c5e024fb3c0d16efd329094f45fc8c1b00b89b6a648731f83a8f7115143ee9e123a0e19a8edd4be5d40274401dd6700b77dc4954efb8d93ac68611ad5d3e6446

Vous effectuez la vérification en fournissant la charge utile du webhook, l'en-tête X-Affirm-Signature et le secret du point de terminaison. Si la vérification échoue, vous pouvez renvoyer une erreur.

📘

Assurez-vous de consulter nos outils recommandés pour commencer.

/*
 Couple of things to set up. Those will also changes based on environment, production vs sandbox.
 - The Authorization header is not useful, and no used as the secret.
 - Get merchant private key. This is actually the secret used for encryption.
 - Get the x-affirm-signature header from the webhook.
 - Make sure to pick-up the body.
 
 We use the CryptoJS library for encryption in this example. Please find the appropriate library based on your programming language.
 
 Webhook signature validation supported:
 - x-affirm-signature
*/

// Can be found in you Affirm dashboard (prod vs sandbox)
let private_key = "A3aut6z2VemhGHPgYF6uBFqczAm4VyyJ";
// Can be found in the webhook payload header
let x_affirm_signature = "t=1597184450,v0=f22309810ee2fc8f7f0ff41e0b1ceb74de98b5077385882e8f93c5d0f5ff86684e38c45531b3d34f07d5dd13a2e7c2c44ddb71d4e67e9a0b781a5976d18e0d42";
// Can be found in the payload (e.g Affirm only supports XML) of the Webhook
let body = "checkout_token=N8R79PUSKRP2UNAJ&created=2020-08-11T22%3A20%3A48.961423&email_address=john.doe%40affirm.com&event=opened&event_timestamp=2020-08-11T22%3A20%3A50.247581&total=60000";

const details = parseHeader(x_affirm_signature, "v0");

console.log(details);

if (!details || details.timestamp === -1) {
  try {
    throw new Error("Unable to extract timestamp and signature from header")
    console.log("Unable to extract timestamp and signature from header")
  } catch (e) {
    console.log(e.name, e.message);
  }
}

if (!details || details.signature === -1) {
   try {
    throw new Error("No signature found with expected scheme")
    console.log("No signature found with expected scheme")
  } catch (e) {
    console.log(e.name, e.message);
  }
}

// This is where the magic happens.
let payload = details.timestamp + "." + body;
let expectedSignature = CryptoJS.HmacSHA512(payload, private_key).toString(CryptoJS.enc.Hex);

console.log(expectedSignature);

if(expectedSignature == details.signature) {
   console.log("Signature match with x-affirm-signature");
} 
    
function parseHeader(header, scheme) {
  if (typeof header !== 'string') {
    return null;
  }

  return header.split(',').reduce(
    (accum, item) => {
      const kv = item.split('=');

      if (kv[0] === 't') {
        accum.timestamp = kv[1];
      }

      if (kv[0] === scheme) {
        accum.signature = kv[1];
      }

      return accum;
    },
    {
      timestamp: -1,
      signature: -1,
    }
  );
}

Affirm génère des signatures à l'aide d'un code d'authentification par message (HMAC) basé sur le hachage avec SHA-512. Pour empêcher les attaques de rétrogradation, vous devez ignorer tous les schémas qui ne sont pas v0.

Étape 1 : Extraire l'horodatage et les signatures de l'en-tête

Divisez l'en-tête, en utilisant le caractère, comme séparateur, pour obtenir une liste d'éléments. Divisez ensuite chaque élément en utilisant le caractère = comme séparateur pour obtenir un préfixe et une paire de valeurs.

La valeur du préfixe t correspond à l'horodatage, et v1 correspond à la ou aux signatures. Vous pouvez supprimer tous les autres éléments.

Étape 2 : Préparer la chaîne signed_payload

Vous y parvenez en concaténant:

  • L'horodatage, c.-à-d. la valeur de "t" (en tant que chaîne).
  • Le caractère ..
  • La charge utile brute réelle ( par exemple. le corps de la demande)

📘

Pour les événements de paiement, les requêtes envoyées par Affirm au point de terminaison de votre webhook sont accompagnées du content-type application/x-www-form-urlencoded et de la version application/x-www-form-urlencoded des données du champ du corps. Les événements de préqualification sont envoyés avec la version content-type application/json et une version JSON des données du champ du corps.

Étape 3 : Déterminer la signature attendue

Calculez un HMAC avec la fonction de hachage SHA512. Utilisez le secret de signature du point de terminaison (votre clé privée) comme clé et utilisez le chaîne signed_payload comme message.

Étape 4 : Comparer les signatures

Comparez la ou les signatures dans l'en-tête à la signature attendue. Si une signature correspond, calculez la différence entre l'horodatage actuel et l'horodatage reçu, puis décidez si la différence est dans le seuil de votre tolérance.

Pour vous protéger contre les attaques temporelles, utilisez une comparaison de chaînes constantes pour comparer la signature attendue à chacune des signatures reçues.