Verificar webhooks
Verifique a assinatura de uma entrega antes de confiar no que ela diz.
Verificar uma entrega
import com.sun.net.httpserver.HttpExchange;import com.sun.net.httpserver.HttpHandler;import java.io.IOException;import java.nio.charset.StandardCharsets;import java.util.Map;import uk.openemail.OpenEmail;import uk.openemail.exception.WebhookSignatureException; public final class WebhookHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { String rawBody = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); String secret = System.getenv("OPENEMAIL_WEBHOOK_SECRET"); int status = 204; try { Map<String, Object> event = OpenEmail.verifyWebhookSignature(rawBody, exchange.getRequestHeaders(), secret); System.out.println(event.get("type") + " " + event.get("id")); } catch (WebhookSignatureException error) { status = 400; } exchange.sendResponseHeaders(status, -1); exchange.close(); }}OpenEmail.verifyWebhookSignature lê o cabeçalho X-OpenEmail-Signature, verifica o seu HMAC sobre a marca temporal e o corpo em tempo constante, e devolve o evento como um Map<String, Object>. O segredo é o que client.webhooks().create devolveu.
Os cabeçalhos são um mapa com nomes em qualquer grafia e um texto ou uma lista de textos como valor, por isso exchange.getRequestHeaders() e os HttpHeaders do Spring servem tal como estão. O valor do próprio cabeçalho também funciona.
Passe o corpo em bruto, exatamente como chegou. Um corpo que foi analisado e codificado de novo já não corresponde à sua assinatura.
Entregas antigas
String signature = "t=1767225600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd";Map<String, String> headers = Map.of("X-OpenEmail-Signature", signature); try { OpenEmail.verifyWebhookSignature("{}", headers, System.getenv("OPENEMAIL_WEBHOOK_SECRET"), Duration.ofMinutes(1));} catch (WebhookSignatureException error) { System.err.println(error.getMessage());}Uma entrega com mais de cinco minutos é recusada, por isso um pedido capturado não pode ser repetido mais tarde. Uma Duration como último argumento altera os cinco minutos, e Duration.ZERO aceita uma entrega de qualquer idade.
- Um cabeçalho em falta ou mal formado, uma assinatura que não corresponde e uma entrega demasiado antiga lançam todos
WebhookSignatureException. - Depois de um segredo ser rodado, uma entrega pode trazer mais do que uma assinatura, e basta que uma corresponda.
- Responda depressa com um estado 2xx. Uma entrega que recebe qualquer outra resposta é tentada de novo mais tarde.