Letzte Änderung: 22. August 2025
Um sicherzustellen, dass die Anfragen, die Ihre Integration von HubSpot erhält, tatsächlich von HubSpot stammen, werden mehrere Header in der Anfrage ausgefüllt. Sie können diese Header zusammen mit Feldern der eingehenden Anfrage verwenden, um die Signatur der Anfrage zu überprüfen. Die zur Überprüfung der Signatur verwendete Methode hängt von der Version der Signatur ab.
  • Um eine Anfrage mit der neuesten Version der HubSpot-Signatur zu validieren, verwenden Sie den X-HubSpot-Signature-V3-Header und folgen Sie den zugehörigen Anweisungen zum Validieren der v3-Version der Signatur .
  • Aus Gründen der Abwärtskompatibilität enthalten Anfragen von HubSpot auch ältere Versionen der Signatur. Um eine ältere Version der Signatur zu validieren, überprüfen Sie den X-HubSpot-Signature-Version-Header und befolgen Sie dann die folgenden Anweisungen, je nachdem, ob die Version v1 oder v2 ist.
In den folgenden Anweisungen erfahren Sie, wie Sie einen Hash-Wert aus dem Client-Geheimnis Ihrer App und den Feldern einer eingehenden Anfrage ableiten. Nachdem Sie den Hash-Wert berechnet haben, vergleichen Sie ihn mit der Signatur. Wenn beide gleich sind, hat die Anfrage die Validierung bestanden. Andernfalls wurde die Anfrage möglicherweise während des Transfers manipuliert, oder jemand spooft Anfragen an Ihren Endpunkt.

Anfragen mit der v1-Anfrageignatur validieren

Wenn Ihre App CRM-Objekt-Events über die Webhooks-API abonniert hat, werden Anfragen von HubSpot mit dem X-HubSpot-Signature-Version-Header auf v1 festgelegt gesendet. Der X-HubSpot-Signature-Header ist ein SHA-256-Hash, der mithilfe des Client-Geheimnisses Ihrer App in Kombination mit den Details der Anfrage erstellt wurde. Um diese Version der Signatur zu überprüfen, führen Sie die folgenden Schritte aus:
  • Erstellen Sie eine Zeichenfolge, die Folgendes miteinander verkettet: Client secret + request body (falls vorhanden).
  • Erstellen Sie einen SHA-256-Hash von der resultierenden Zeichenfolge.
  • Vergleichen Sie den Hash-Wert mit dem Wert des X-HubSpot-Signature-Headers:
    • Wenn sie gleich sind, hat diese Anfrage die Überprüfung bestanden.
    • Wenn diese Werte nicht übereinstimmen, wurde diese Anfrage möglicherweise während des Transfers manipuliert, oder jemand spooft Anfragen an Ihren Endpunkt.
Beispiel für eine Anfrage mit einem Text:
//Client secret : yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
// Request body: [
{"eventId":1,"subscriptionId":12345,"
portalId":62515",
occurredAt":1564113600000",
subscriptionType":"contact.creation",
"attemptNumber":0,
"objectId":123,
"changeSource":"CRM",
"changeFlag":"NEW",
"appId":54321}
]

Beispiele für v1-Anfragesignaturen

Beispiel für Python

NOTE: This is only an example for generating the expected hash.
You will need to compare this expected hash with the actual hash in the
X-HubSpot-Signature header.

>>> import hashlib

>>> client_secret = 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy'
>>> request_body = '[{"eventId":1,"subscriptionId":12345,"portalId":62515,"occurredAt":1564113600000,"subscriptionType":"contact.creation","attemptNumber":0,"objectId":123,"changeSource":"CRM","changeFlag":"NEW","appId":54321}]'
>>> source_string = client_secret + request_body
>>> source_string
'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy[{"eventId":1,"subscriptionId":12345,"portalId":62515,"occurredAt":1564113600000,"subscriptionType":"contact.creation","attemptNumber":0,"objectId":123,"changeSource":"CRM","changeFlag":"NEW","appId":54321}]'
>>> hashlib.sha256(source_string).hexdigest()
'232db2615f3d666fe21a8ec971ac7b5402d33b9a925784df3ca654d05f4817de'

Beispiel für Ruby

NOTE: This is only an example for generating the expected hash.
You will need to compare this expected hash with the actual hash in the
X-HubSpot-Signature header.

irb(main):003:0> require 'digest'
=> true
irb(main):004:0> client_secret = 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy'
=> "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
irb(main):005:0> request_body = '[{"eventId":1,"subscriptionId":12345,"portalId":62515,"occurredAt":1564113600000,"subscriptionType":"contact.creation","attemptNumber":0,"objectId":123,"changeSource":"CRM","changeFlag":"NEW","appId":54321}]'
=> "[{\"eventId\":1,\"subscriptionId\":12345,\"portalId\":62515,\"occurredAt\":1564113600000,\"subscriptionType\":\"contact.creation\",\"attemptNumber\":0,\"objectId\":123,\"changeSource\":\"CRM\",\"changeFlag\":\"NEW\",\"appId\":54321}]"
irb(main):006:0> source_string = client_secret + request_body
=> "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy[{\"eventId\":1,\"subscriptionId\":12345,\"portalId\":62515,\"occurredAt\":1564113600000,\"subscriptionType\":\"contact.creation\",\"attemptNumber\":0,\"objectId\":123,\"changeSource\":\"CRM\",\"changeFlag\":\"NEW\",\"appId\":54321}]"
irb(main):007:0> Digest::SHA256.hexdigest source_string
=> "232db2615f3d666fe21a8ec971ac7b5402d33b9a925784df3ca654d05f4817de"

Beispiel für Node.js

NOTE: This is only an example for generating the expected hash.
You will need to compare this expected hash with the actual hash in the
X-HubSpot-Signature header.

> const crypto = require('crypto')
undefined
> client_secret = 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy'
'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy'
> request_body = '[{"eventId":1,"subscriptionId":12345,"portalId":62515,"occurredAt":1564113600000,"subscriptionType":"contact.creation","attemptNumber":0,"objectId":123,"changeSource":"CRM","changeFlag":"NEW","appId":54321}]'
'[{"eventId":1,"subscriptionId":12345,"portalId":62515,"occurredAt":1564113600000,"subscriptionType":"contact.creation","attemptNumber":0,"objectId":123,"changeSource":"CRM","changeFlag":"NEW","appId":54321}]'
> source_string = client_secret + request_body
'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy[{"eventId":1,"subscriptionId":12345,"portalId":62515,"occurredAt":1564113600000,"subscriptionType":"contact.creation","attemptNumber":0,"objectId":123,"changeSource":"CRM","changeFlag":"NEW","appId":54321}]'
> hash = crypto.createHash('sha256').update(source_string).digest('hex')
'232db2615f3d666fe21a8ec971ac7b5402d33b9a925784df3ca654d05f4817de'

Beispiel für Java

// NOTE: This is only an example for generating the expected hash.
// You will need to compare this expected hash with the actual hash in the
// X-HubSpot-Signature header.

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.UnsupportedEncodingException;

public class HubSpotSignatureValidator {

    public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        String clientSecret = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy";
        String requestBody = "[{\"eventId\":1,\"subscriptionId\":12345,\"portalId\":62515,\"occurredAt\":1564113600000,\"subscriptionType\":\"contact.creation\",\"attemptNumber\":0,\"objectId\":123,\"changeSource\":\"CRM\",\"changeFlag\":\"NEW\",\"appId\":54321}]";

        String sourceString = clientSecret + requestBody;
        System.out.println("Source string: " + sourceString);

        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(sourceString.getBytes("UTF-8"));

        // Convert to hex string (Java 17+)
        String hexString = java.util.HexFormat.of().formatHex(hash);

        System.out.println("Hash: " + hexString);
        // Output: 232db2615f3d666fe21a8ec971ac7b5402d33b9a925784df3ca654d05f4817de
    }
}
Der resultierende Hash würde folgendermaßen aussehen:232db2615f3d666fe21a8ec971ac7b5402d33b9a925784df3ca654d05f4817de

Anfragen mit der v2-Anfragesignatur validieren

Wenn Ihre App Daten aus einer Webhook-Aktion in einem Workflow verarbeitet oder wenn Sie Daten für eine benutzerdefinierte CRM-Karte zurückgeben, wird die Anfrage von HubSpot mit dem X-HubSpot-Signature-Version-Header auf v2 festgelegt gesendet. Der X-HubSpot-Signature-Header ist ein SHA-256-Hash, der mithilfe des Client-Geheimnisses Ihrer App in Kombination mit den Details der Anfrage erstellt wurde. Um diese Signatur zu überprüfen, führen Sie die folgenden Schritte aus:
  • Erstellen Sie eine Zeichenfolge, die Folgendes miteinander verkettet: Client secret + http method+ URI + request body (falls vorhanden).
  • Erstellen Sie einen SHA-256-Hash von der resultierenden Zeichenfolge.
  • Vergleichen Sie den Hash-Wert mit der Signatur.
    • Wenn sie gleich sind, hat diese Anfrage die Überprüfung bestanden.
    • Wenn diese Werte nicht übereinstimmen, wurde diese Anfrage möglicherweise während des Transfers manipuliert, oder jemand spooft Anfragen an Ihren Endpunkt.
Hinweise:
  • Der zur Erstellung der Quellzeichenfolge verwendete URI muss genau mit der ursprünglichen Anfrage übereinstimmen, einschließlich des Protokolls. Wenn Sie Schwierigkeiten bei der Validierung der Signatur haben, stellen Sie sicher, dass alle Abfrageparameter in genau der gleichen Reihenfolge sind wie in der ursprünglichen Anfrage aufgeführt.
  • Die Quellzeichenfolge sollte vor der Berechnung des SHA-256-Hash-Werts UTF-8-codiert werden.

Beispiel für eine GET-Anfrage

Für eine GET-Anfrage benötigen Sie das Client-Geheimnis Ihrer App und bestimmte Felder aus den Metadaten Ihrer Anfrage. Diese Felder sind unten mit Platzhalterwerten aufgeführt:
  • Client-Geheimnis: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
  • HTTP-Methode: GET
  • URI: https://www.example.com/webhook_uri
  • Anfragetext: ""
Die resultierende verkettete Zeichenfolge würde folgendermaßen aussehen: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyyGEThttps://www.example.com/webhook_uri Nach der Berechnung eines SHA-256-Hashes der obigen verketteten Zeichenfolge würde die resultierende Signatur, die mit der im Header übereinstimmen sollte, wie folgt aussehen: eee2dddcc73c94d699f5e395f4b9d454a069a6855fbfa152e91e88823087200e

Beispiel für eine POST-Anfrage

Für eine POST-Anfrage benötigen Sie das Client-Geheimnis Ihrer App, bestimmte Felder aus den Metadaten Ihrer Anfrage und eine Zeichenfolgendarstellung des Anfragetextes (z. B. die Verwendung von JSON.stringify(request.body) für einen Node.js-Dienst). Diese Felder sind unten mit Platzhalterwerten aufgeführt:
  • Client-Geheimnis: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
  • HTTP-Methode: POST
  • URI: https://www.example.com/webhook_uri
  • Anfragetext: {"example_field":"example_value"}
Die resultierende verkettete Zeichenfolge würde folgendermaßen aussehen: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyyPOSThttps://www.example.com/webhook_uri{"example_field":"example_value"} Nach der Berechnung eines SHA-256-Hashes der obigen verketteten Zeichenfolge würde die resultierende Signatur, die mit der im Header übereinstimmen sollte, wie folgt aussehen:9569219f8ba981ffa6f6f16aa0f48637d35d728c7e4d93d0d52efaa512af7900 Nach dem \[SHA-ing]] der Signatur können Sie die resultierende erwartete Signatur mit der im x-hubspot-signature-Header der Anfrage angegebenen Signatur vergleichen: Im folgenden Snippet wird beschrieben, wie Sie eine v2-Anfragevalidierung für eine GET-Anfrage integrieren können, wenn Sie einen Express-Server ausführen, um eingehende Anfragen zu verarbeiten. Beachten Sie, dass der folgende Code-Block ein Beispiel ist und bestimmte Abhängigkeiten auslässt, die Sie möglicherweise benötigen, um einen voll funktionsfähigen Express-Dienst auszuführen. Vergewissern Sie sich, dass Sie die neuesten stabilen und sicheren Bibliotheken ausführen, wenn Sie die Anfragevalidierung für Ihren spezifischen Dienst implementieren.

Beispiele für v2-Anfragesignaturen

Beispiel für Node.js

// Introduce any dependencies. Only several dependencies related to this example are included below:
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const app = express();

// Add any custom handling or setup code for your Node.js service here.
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// Example Node.js request validation code.
app.get('/example-service', (request, response, next) => {
  const { url, method, headers, hostname } = request;

  const requestSignature = headers['x-hubspot-signature'];

  // Compute expected signature
  const uri = `https://${hostname}${url}`;
  const encodedString = Buffer.from(
    `${process.env.CLIENT_SECRET}${method}${uri}`,
    'ascii'
  ).toString('utf-8');
  const expectedSignature = crypto
    .createHash('sha256')
    .update(encodedString)
    .digest('hex');

  console.log('Expected signature: %s', requestSignature);
  console.log('Request signature: %s', expectedSignature);

  // Add your custom handling to compare request signature to expected signature
  if (requestSignature !== expectedSignature) {
    console.log('Request of signature does NOT match!');
    response.status(400).send('Bad request');
  } else {
    console.log('Request of signature matches!');
    response.status(200).send();
  }
});

Beispiel für Java

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.StandardCharsets;

public class HubSpotV2SignatureValidator {

    public static boolean validateV2Signature(String clientSecret, String method,
                                                String uri,
                                                String receivedSignature) {
        try {
          // Create concatenated string: client_secret + method + uri + body
          String sourceString = clientSecret + method + uri;
          System.out.println("Source string: " + sourceString);

          // Create SHA-256 hash
          MessageDigest digest = MessageDigest.getInstance("SHA-256");
          byte[] hash = digest.digest(sourceString.getBytes(StandardCharsets.UTF_8));

          // Convert to hex string (Java 17+)
          String expectedSignature = java.util.HexFormat.of().formatHex(hash);

          // Compare signatures using constant-time comparison
          return MessageDigest.isEqual(expectedSignature.getBytes(), receivedSignature.getBytes());

        } catch (NoSuchAlgorithmException e) {
          throw new RuntimeException("SHA-256 algorithm not available", e);
        }
      }

      // Example usage
      public static void main(String[] args) {
        String clientSecret = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy";
        String method = "GET";
        String uri = "https://www.example.com/webhook_uri";

        // Expected signature: 9569219f8ba981ffa6f6f16aa0f48637d35d728c7e4d93d0d52efaa512af7900
        String expectedSignature = "9569219f8ba981ffa6f6f16aa0f48637d35d728c7e4d93d0d52efaa512af7900";

        boolean isValid = validateV2Signature(clientSecret, method, uri, expectedSignature);
        if (isValid) {
          System.out.println("Signature is valid!");
        }
        // Proceed with any request processing as needed.
       else {
        System.out.println("Signature is invalid!");
        // Add any rejection logic here. e.g, throw 400
        }
      }
}

v3-Anfragesignaturen validieren

Der X-HubSpot-Signature-v3-Header ist ein HMAC SHA-256-Hash, der mit dem Client-Geheimnis Ihrer App in Kombination mit Details der Anfrage erstellt wird. Sie enthält auch einen X-HubSpot-Request-Timestamp-Header. Wenn Sie eine Anfrage mit dem X-HubSpot-Signatur-v3-Header validieren, müssen Sie:
  • Die Anfrage ablehnen, wenn der Zeitstempel älter als 5 Minuten ist.
  • Im Anfrage-URI eines der URL-codierten Zeichen, die in der folgenden Tabelle aufgeführt sind. Sie müssen das Fragezeichen, das den Anfang der Abfragezeichenfolge angibt, nicht decodieren.
Codierter WertDecodierter Wert
%3A:
%2F/
%3F?
%40@
%21!
%24$
%27'
%28(
%29)
%2A*
%2C,
%3B;
  • Erstellen Sie eine Zeichenfolge, die Folgendes miteinander verkettet: requestMethod + requestUri + requestBody + Zeitstempel (falls vorhanden). Der Zeitstempel wird vom X-HubSpot-Request-Timestamp-Header bereitgestellt.
  • Erstellen Sie einen HMAC SHA-256-Hash der resultierenden Zeichenfolge, indem Sie das Anwendungsgeheimnis als das Geheimnis für die HMAC SHA-256-Funktion verwenden.
  • Base64-Codieren Sie das Ergebnis der HMAC-Funktion.
  • Vergleichen Sie den Hash-Wert mit der Signatur. Wenn sie gleich sind, wurde diese Anfrage als von HubSpot stammend verifiziert. Es wird empfohlen, einen Zeichenfolgenvergleich mit Zeitkonstant zu verwenden, um sich vor Rechenzeitangriffen zu schützen.
Die Codeausschnitte im folgenden Abschnitt beschreiben, wie Sie die v3-Anfragevalidierung für eine POST-Anfrage integrieren können, wenn Sie einen Backend-Service verwenden, um eingehende Anfragen zu verarbeiten. Beachten Sie, dass die folgenden Code-Blocks bestimmte Abhängigkeiten auslassen, die Sie möglicherweise benötigen, um einen voll funktionsfähigen Backend-Dienst auszuführen. Vergewissern Sie sich, dass Sie die neuesten stabilen und sicheren Bibliotheken ausführen, wenn Sie die Anfragevalidierung für Ihren spezifischen Dienst implementieren.

Beispiele für v3-Anfragesignaturen

Beispiel für Node.js

// Introduce any dependencies. Only several dependencies related to this example are included below:
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const app = express();
const port = process.env.PORT || 4000;

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.post('/webhook-test', (request, response) => {
  response.status(200).send('Received webhook subscription trigger');

  const { url, method, body, headers, hostname } = request;

  // Parse headers needed to validate signature
  const signatureHeader = headers['x-hubspot-signature-v3'];
  const timestampHeader = headers['x-hubspot-request-timestamp'];

  // Validate timestamp
  const MAX_ALLOWED_TIMESTAMP = 300000; // 5 minutes in milliseconds
  const currentTime = Date.now();
  if (currentTime - timestampHeader > MAX_ALLOWED_TIMESTAMP) {
    console.log('Timestamp is invalid, reject request');
    // Add any rejection logic here
  }

  // Concatenate request method, URI, body, and header timestamp
  const uri = `https://${hostname}${url}`;
  const rawString = `${method}${uri}${JSON.stringify(body)}${timestampHeader}`;

  // Create HMAC SHA-256 hash from resulting string above, then base64-encode it
  const hashedString = crypto
    .createHmac('sha256', process.env.CLIENT_SECRET)
    .update(rawString)
    .digest('base64');

  // Validate signature: compare computed signature vs. signature in header
  if (
    crypto.timingSafeEqual(
      Buffer.from(hashedString),
      Buffer.from(signatureHeader)
    )
  ) {
    console.log('Signature matches! Request is valid.');
    // Proceed with any request processing as needed.
  } else {
    console.log('Signature does not match: request is invalid');
    // Add any rejection logic here.
  }
});

Beispiel für Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class HubSpotV3SignatureValidator {

    // 5 minutes in milliseconds
    private static final long MAX_ALLOWED_TIMESTAMP = 300000;

      public static boolean validateV3Signature(String clientSecret, String method,
                                                String uri, String requestBody,
                                                long timestamp, String receivedSignature) {
        try {
          // Validate timestamp (reject if older than 5 minutes)
          long currentTime = System.currentTimeMillis();
          if (currentTime - timestamp > MAX_ALLOWED_TIMESTAMP) {
            System.out.println("Timestamp is invalid, rejecting request");
            return false;
          }

          // Create concatenated string: method + uri + body + timestamp
          String rawString = method + uri + requestBody + timestamp;
          System.out.println("Raw string: " + rawString);

          // Create HMAC SHA-256 hash
          Mac hmacSha256 = Mac.getInstance("HmacSHA256");
          SecretKeySpec secretKey = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
          hmacSha256.init(secretKey);

          byte[] hash = hmacSha256.doFinal(rawString.getBytes(StandardCharsets.UTF_8));

          // Base64 encode the result
          String expectedSignature = Base64.getEncoder().encodeToString(hash);

          // Compare signatures using constant-time comparison
          return MessageDigest.isEqual(expectedSignature.getBytes(), receivedSignature.getBytes());

        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
          throw new RuntimeException("Error creating HMAC SHA-256 hash", e);
        }
      }

      // Example usage
      public static void main(String[] args) {
        String clientSecret = "cfc68c0b-4b4e-4ef8-b764-95350e4ea479";
        String method = "POST";
        String uri = "https://webhook.site/335453f5-94b3-49d9-b684-a55354d4b8df";
        String requestBody = "[{\"eventId\":531833541,\"subscriptionId\":3923621,\"portalId\":48807704,\"appId\":16111050,\"occurredAt\":1752613920733,\"subscriptionType\":\"contact.creation\",\"attemptNumber\":0,\"objectId\":138017612137,\"changeFlag\":\"CREATED\",\"changeSource\":\"CRM_UI\",\"sourceId\":\"userId:76023669\"}]";
        long timestamp = 1752613922216L; // Example timestamp in milliseconds

        // This would typically come from the X-HubSpot-Signature-v3 header
        String signatureFromHeader = "gbj1XPRvUt0noT7i7fXfTzOD4sLzQmf0VT28ZYq0EYg=";

        boolean isValid = validateV3Signature(clientSecret, method, uri, requestBody, timestamp, signatureFromHeader);
        if(isValid) {
          System.out.println("Signature is valid! Proceed with request processing.");
        }
        // Proceed with any request processing as needed.
       else {
        System.out.println("Signature is invalid! Reject the request.");
        // Add any rejection logic here, e.g., throw 400 Bad Request
        }
      }
}