import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public final class WebhookVerifier {
private static final long MAX_SKEW_MS = 300_000L;
public static boolean verify(String secret, String timestamp, byte[] rawBody, String signature)
throws Exception {
long ts = Long.parseLong(timestamp);
if (Math.abs(ts - System.currentTimeMillis()) > MAX_SKEW_MS) {
return false;
}
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] tsBytes = timestamp.getBytes(StandardCharsets.UTF_8);
byte[] message = new byte[tsBytes.length + rawBody.length];
System.arraycopy(tsBytes, 0, message, 0, tsBytes.length);
System.arraycopy(rawBody, 0, message, tsBytes.length, rawBody.length);
String expected = toHex(mac.doFinal(message));
return MessageDigest.isEqual(
expected.getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8));
}
private static String toHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}