Vérifier et valider les signatures de Webhook

Apprenez à vérifier et valider les signatures de webhook pour des intégrations sécurisées avec Affirm. Prévenez les attaques de relecture et assurez l’intégrité des données avec les méthodes de vérification HMAC-SHA512.

Aperçu

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

The X-Affirm-Signature header contains a timestamp and one or more signatures. The timestamp is prefixed by t=, and each signature is prefixed by a scheme. Schemes start with v, followed by an integer. Currently, the only valid signature scheme is v0.

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

You perform the verification by providing the webhook payload, the X-Affirm-Signature header, and the endpoint’s secret. If verification fails, you can return an error.

📘

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 generates signatures using a hash-based message authentication code (HMAC) with SHA-512. To prevent downgrade attacks, you should ignore all schemes that are not v0.


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

Split the header, using the, character as the separator, to get a list of elements. Then split each element, using the = character as the separator, to get a prefix and value pair.

The value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature(s). You can discard all other elements.

É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).
  • The character ..
  • La charge utile brute réelle ( par exemple. le corps de la demande)
📘

For checkout events, the requests sent from Affirm to your webhook endpoint come with the content-type application/x-www-form-urlencoded and a application/x-www-form-urlencoded version of the data in the body field. Prequal events are sent with the content-type application/json and a JSON version of the data in the body field.

Étape 3 : Déterminer la signature attendue

Compute an HMAC with the SHA512 hash function. Use the endpoint’s signing secret (your private key) as the key, and use the signed_payload string as the 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.