Skip to main content

Webhook Signature Verification

NOTE

Since outbound webhook connections are not necessarily sent from a dedicated IP address, the webhook signature is the only way to prove that this payload is genuine and is not spoofed by an attacker. All customers using webhooks should implement the signature verification as described below in order to protect themselves against these kinds of spoofing attacks.

Extract the signature and event body

Each webhook event will have an x-sha2-signature header. This contains the webhook event’s signature in hexadecimal format.

Make sure you use the raw event request body. If you parse it from JSON first, the fields may be reordered.

Compute the expected signature

You can get the expected signature of the webhook by computing the HMAC of the event request body using the SHA256 algorithm, using the webhook’s secret token as the key.

Compare the signatures

A webhook event is valid if the signature from the header is equal to the expected signature you compute for it. Make sure signatures are both in hexadecimal, before comparing.

warning

You should use a constant time equality function from a cryptographic library to prevent timing attacks. A timing attack is where a malicious user measures the small time differences taken to compare the signatures over many requests, to eventually work out the expected signature for a webhook event. A constant time equality function prevents this by always taking the same amount of time when comparing strings of a particular length.

Signature verification reference implementation examples

package com.entrustdatacard.intellitrust.webhooks;

import org.apache.commons.codec.binary.Hex;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

public class WebhookSignatureVerifier {

public void verifyPayload(String rawEventBody, String hexSignature, String webhookToken) {
Mac sha256Hmac;
String eventSignature = "";

try {
sha256Hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(webhookToken.getBytes(), "HmacSHA256");
sha256Hmac.init(secretKey);
eventSignature = new String(Hex.encodeHex(sha256Hmac.doFinal(rawEventBody.getBytes(StandardCharsets.UTF_8))));
} catch (Exception e) {
//Exception-Error while generating signature
}

if (!MessageDigest.isEqual(eventSignature.getBytes(), hexSignature.getBytes())) {
//Exception-Invalid webhook signature found
}
}
}

Event payload schema

{
"resource": {
"id": "<UUID of the changed resource>",
"href": "<URI to read the updated resource>"
},
"resourceType": "credential",
"event": "credential.create | credential.update | credential.delete"
}

Fetching updated resource details

Post successful signature verification, the resource details can be fetched using the API endpoint referenced by the "href" attribute of event payload in case of create and update events.

package com.entrustdatacard.intellitrust.webhooks;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class WebhookEventProcessor {

public void processWebhookEvent(String rawEventBody, String hexSignature, String webhookToken) {
try {
verifyPayload(rawEventBody, hexSignature, webhookToken);
} catch (Exception e) {
// Throw Exception, payload not verified.
}

JSONObject payload = new JSONObject(rawEventBody.toString());
JSONObject resource = payload.getJSONObject("resource");
String apiEndpoint = resource.get("href").toString();

try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest httpGet = HttpRequest.newBuilder()
.GET().uri(URI.create(apiEndpoint))
.header("Accept", "application/json")
.header("Authorization", "<API_AUTH_TOKEN>").build(); // Replace with your API token.

HttpResponse<String> response = client.send(httpGet, HttpResponse.BodyHandlers.ofString());
// Process the response as needed.
} catch (Exception e) {
// Handle API response error.
}
}
}

Notification delivery acknowledgement

Upon receiving a webhook notification, acknowledge the successful delivery by responding with an HTTP 200 OK status code within 15 seconds. Otherwise, the notification delivery will be reattempted for a maximum of 5 times. If a retry fails on the 5th attempt, the webhook will be disabled. Retry schedule:

  • 30 seconds after the first attempt
  • 2 minutes after the first attempt
  • 15 minutes after the first attempt
  • 2 hours after the first attempt
  • 10 hours after the first attempt