Ir a la documentación
Java

Verificar webhooks

Comprueba la firma de una entrega antes de fiarte de lo que dice.

Verificar una entrega

WebhookHandler.java
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 lee la cabecera X-OpenEmail-Signature, comprueba su HMAC sobre la marca de tiempo y el cuerpo en tiempo constante, y devuelve el evento como un Map<String, Object>. El secreto es el que devolvió client.webhooks().create.

Las cabeceras son un mapa con nombres en cualquier grafía y un texto o una lista de textos como valor, así que exchange.getRequestHeaders() y los HttpHeaders de Spring encajan tal cual. El valor de la propia cabecera también sirve.

Pasa el cuerpo sin procesar, tal como llegó. Un cuerpo que se analizó y se volvió a codificar ya no coincide con su firma.

Entregas antiguas

Tolerance.java
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());}

Una entrega con más de cinco minutos se rechaza, así que una petición capturada no se puede reproducir más tarde. Una Duration como último argumento cambia esos cinco minutos, y Duration.ZERO acepta una entrega de cualquier antigüedad.

  • Una cabecera ausente o mal formada, una firma que no coincide y una entrega demasiado antigua lanzan todas WebhookSignatureException.
  • Después de rotar un secreto, una entrega puede llevar más de una firma, y basta con que coincida una.
  • Responde rápido con un estado 2xx. Una entrega que recibe cualquier otra respuesta se reintenta más tarde.