I translated the Python Webhook code on the site into Java and my code is below. Any thoughts on why this isn’t pulling values from Fireblocks? Is my public key correct? I am using the https://api.fireblocks.io/v1 instance:
import com.bakkt.custody.provider.model.Event;
import com.bakkt.custody.provider.model.ExternalWalletAssetRemoved;
import com.bakkt.custody.provider.model.TransactionCreated;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
import io.micronaut.http.annotation.Consumes;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Controller("/webhook")
public class WebhookController {
private static final Logger LOG = LoggerFactory.getLogger(WebhookController.class);
private PublicKey fireblocksPublicKey;
private final ObjectMapper objectMapper;
public WebhookController(ObjectMapper objectMapper) throws Exception {
this.objectMapper = objectMapper;
String fireblocksPublicKeyStr =
"""
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0+6wd9OJQpK60ZI7qnZG
jjQ0wNFUHfRv85Tdyek8+ahlg1Ph8uhwl4N6DZw5LwLXhNjzAbQ8LGPxt36RUZl5
YlxTru0jZNKx5lslR+H4i936A4pKBjgiMmSkVwXD9HcfKHTp70GQ812+J0Fvti/v
4nrrUpc011Wo4F6omt1QcYsi4GTI5OsEbeKQ24BtUd6Z1Nm/EP7PfPxeb4CP8KOH
clM8K7OwBUfWrip8Ptljjz9BNOZUF94iyjJ/BIzGJjyCntho64ehpUYP8UJykLVd
CGcu7sVYWnknf1ZGLuqqZQt4qt7cUUhFGielssZP9N9x7wzaAIFcT3yQ+ELDu1SZ
dE4lZsf2uMyfj58V8GDOLLE233+LRsRbJ083x+e2mW5BdAGtGgQBusFfnmv5Bxqd
HgS55hsna5725/44tvxll261TgQvjGrTxwe7e5Ia3d2Syc+e89mXQaI/+cZnylNP
SwCCvx8mOM847T0XkVRX3ZrwXtHIA25uKsPJzUtksDnAowB91j7RJkjXxJcz3Vh1
4k182UFOTPRW9jzdWNSyWQGl/vpe9oQ4c2Ly15+/toBo4YXJeDdDnZ5c/O+KKadc
IMPBpnPrH/0O97uMPuED+nI6ISGOTMLZo35xJ96gPBwyG5s2QxIkKPXIrhgcgUnk
tSM7QYNhlftT4/yVvYnk0YcCAwEAAQ==
-----END PUBLIC KEY-----
"""
.replace("-----BEGIN PUBLIC KEY-----\n", "")
.replace("-----END PUBLIC KEY-----\n", "")
.replace("\n", "");
byte[] encoded = Base64.getDecoder().decode(fireblocksPublicKeyStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
this.fireblocksPublicKey = kf.generatePublic(keySpec);
}
@Post
@Consumes(MediaType.APPLICATION_JSON)
public HttpResponse<String> handleWebhook(
HttpRequest<String> request, @Header("Fireblocks-Signature") String fireblocksSignature)
throws JsonProcessingException {
String body = request.getBody().orElse("");
LOG.info("Incoming request body: {}", body);
try {
boolean isSignatureValid =
verifySignature(body.getBytes(StandardCharsets.UTF_8), fireblocksSignature);
if (!isSignatureValid) {
return HttpResponse.unauthorized();
}
LOG.info("Received request with body: {} and signature: {}", body, fireblocksSignature);
Event event = objectMapper.readValue(body, Event.class);
if (event instanceof ExternalWalletAssetRemoved) {
ExternalWalletAssetRemoved externalWalletAssetRemovedEvent =
(ExternalWalletAssetRemoved) event;
LOG.info(
"Received External Wallet Asset Removed event: {}", externalWalletAssetRemovedEvent);
} else if (event instanceof TransactionCreated) {
TransactionCreated transactionCreatedEvent = (TransactionCreated) event;
LOG.info("Received Transaction Created event: {}", transactionCreatedEvent);
} else {
LOG.warn("Received unknown event: {}", event);
}
return HttpResponse.ok("Event received");
} catch (Exception e) {
LOG.error("Error handling webhook", e);
return HttpResponse.serverError("Error handling webhook");
}
}
private boolean verifySignature(byte[] data, String signature) throws Exception {
Signature sig = Signature.getInstance("SHA512withRSA");
sig.initVerify(fireblocksPublicKey);
sig.update(data);
LOG.info("Verifying signature...");
boolean isVerified = sig.verify(Base64.getUrlDecoder().decode(signature));
LOG.info("Signature verification result: {}", isVerified);
return isVerified;
}
}