Перейти к документации
Java

Проверка вебхуков

Проверьте подпись доставки, прежде чем доверять её содержимому.

Проверка доставки

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 читает заголовок X-OpenEmail-Signature, проверяет его HMAC по метке времени и телу за постоянное время и возвращает событие в виде Map<String, Object>. Секрет: тот, который вернул client.webhooks().create.

Заголовки представляют собой map с именами в любом регистре и текстом или списком текстов в качестве значения, поэтому exchange.getRequestHeaders() и HttpHeaders из Spring подходят как есть. Значение самого заголовка тоже подходит.

Передавайте необработанное тело в точности таким, каким оно пришло. Тело, которое разобрали и закодировали заново, уже не совпадает со своей подписью.

Старые доставки

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

Доставка старше пяти минут отклоняется, поэтому перехваченный запрос нельзя воспроизвести позже. Duration последним аргументом меняет эти пять минут, а Duration.ZERO принимает доставку любого возраста.

  • Отсутствующий или некорректный заголовок, несовпадающая подпись и слишком старая доставка выбрасывают WebhookSignatureException.
  • После смены секрета доставка может нести больше одной подписи, и достаточно совпадения любой из них.
  • Отвечайте статусом 2xx быстро. Доставка, получившая любой другой ответ, повторяется позже.