Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/mercadopago/sdk-java/llms.txt

Use this file to discover all available pages before exploring further.

When Mercado Pago delivers a webhook notification to your endpoint, it signs the request with an HMAC-SHA256 digest so you can confirm the event genuinely originated from Mercado Pago and was not tampered with in transit. The WebhookSignatureValidator class in the SDK reconstructs that digest locally using your application’s secret and performs a constant-time comparison (via MessageDigest.isEqual) against the value in the incoming x-signature header — protecting against both forgery and timing attacks. This guide explains the signature scheme, all three validator overloads, error handling, and a complete Spring Boot example.

How Mercado Pago webhook signatures work

Every signed webhook request carries three pieces of data you need for validation:
SourceKeyDescription
HTTP headerx-signatureContains the timestamp (ts) and the HMAC hash (v1), e.g. ts=1719000000000,v1=abc123...
HTTP headerx-request-idA unique request correlation identifier provided by Mercado Pago.
Query parameterdata.idThe identifier of the resource that triggered the notification (e.g., a payment ID).
The validator builds a manifest string by concatenating whichever of those values are present:
id:<data.id>;request-id:<x-request-id>;ts:<timestamp>;
It then computes HMAC-SHA256(secret, manifest) and compares the hex result against the v1 hash from the header. If they match and the optional timestamp tolerance check passes, the request is considered authentic.

Getting your webhook secret

1

Open Tus Integraciones

Log in to the Mercado Pago Developer Panel and navigate to Tus Integraciones.
2

Select your application

Click on the application that will receive webhook notifications.
3

Go to Webhooks configuration

In the left-hand menu, click Webhooks. You will see the Secret signature field — copy this value.
4

Store the secret securely

Store the secret in an environment variable or your secrets manager. Never hard-code it in source code.
String webhookSecret = System.getenv("MP_WEBHOOK_SECRET");

Validation overloads

WebhookSignatureValidator is a stateless utility class with three static validate(...) overloads. All of them throw MPInvalidWebhookSignatureException on failure and return void on success.

1. Basic validation

The simplest overload — validates signature and hash, no replay-attack window:
import com.mercadopago.webhook.WebhookSignatureValidator;
import com.mercadopago.exceptions.MPInvalidWebhookSignatureException;

String xSignature = request.getHeader("x-signature");
String xRequestId = request.getHeader("x-request-id");
String dataId     = request.getParameter("data.id");
String secret     = System.getenv("MP_WEBHOOK_SECRET");

WebhookSignatureValidator.validate(xSignature, xRequestId, dataId, secret);

2. With replay-attack tolerance

Supply a Duration tolerance window. If the timestamp in x-signature is older than now ± tolerance, the validator throws SignatureFailureReason.TIMESTAMP_OUT_OF_TOLERANCE. This protects against replay attacks where a genuine but old notification is replayed by an attacker.
import com.mercadopago.webhook.WebhookSignatureValidator;
import com.mercadopago.exceptions.MPInvalidWebhookSignatureException;
import java.time.Duration;

WebhookSignatureValidator.validate(
    xSignature,
    xRequestId,
    dataId,
    secret,
    Duration.ofMinutes(5)   // reject notifications older than 5 minutes
);
A tolerance of 5 minutes is a reasonable default. Make sure your server clock is synchronized with NTP — significant clock drift will cause legitimate notifications to fail the tolerance check.

3. Full overload — custom versions and clock

The seven-argument overload exposes every knob, primarily for advanced use cases and testing:
import com.mercadopago.webhook.WebhookSignatureValidator;
import com.mercadopago.exceptions.MPInvalidWebhookSignatureException;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.function.Supplier;

List<String> supportedVersions = List.of("v1");     // default; update when MP ships a new version
Supplier<Instant> clock = Instant::now;              // swap with a fixed clock in tests

WebhookSignatureValidator.validate(
    xSignature,
    xRequestId,
    dataId,
    secret,
    Duration.ofMinutes(5),
    supportedVersions,
    clock
);
ParameterTypeDescription
xSignatureStringRaw value of the x-signature header.
xRequestIdStringValue of the x-request-id header; may be null.
dataIdStringValue of the data.id query parameter; may be null.
secretStringYour application’s webhook secret from Tus Integraciones. Must not be null.
toleranceDurationMaximum allowed age of the notification; null disables the check.
supportedVersionsList<String>Ordered list of signature versions to accept. Defaults to ["v1"].
clockSupplier<Instant>Clock for the tolerance check. Defaults to Instant::now.

Error handling

On any validation failure, WebhookSignatureValidator throws MPInvalidWebhookSignatureException. Inspect getReason() to understand why it failed:
import com.mercadopago.exceptions.MPInvalidWebhookSignatureException;
import com.mercadopago.exceptions.SignatureFailureReason;

try {
    WebhookSignatureValidator.validate(xSignature, xRequestId, dataId, secret);
} catch (MPInvalidWebhookSignatureException e) {
    SignatureFailureReason reason = e.getReason();
    String requestId             = e.getRequestId();   // for log correlation
    String timestamp             = e.getTimestamp();   // ts value from the header, if parsed

    // Log for internal correlation — do NOT expose reason in the HTTP response
    logger.warn("Webhook validation failed: reason={}, requestId={}, ts={}",
        reason, requestId, timestamp);

    // Respond HTTP 401 to Mercado Pago
    response.setStatus(401);
}

SignatureFailureReason values

ReasonMeaning
MISSING_SIGNATURE_HEADERThe x-signature header was absent, empty, or whitespace-only.
MALFORMED_SIGNATURE_HEADERThe header did not conform to the expected ts=...,v1=... format.
MISSING_TIMESTAMPThe header parsed correctly but no ts= component was present.
MISSING_HASHNo hash was found for any of the supported versions. This may indicate Mercado Pago has introduced a new signature version — update the SDK.
SIGNATURE_MISMATCHThe computed HMAC did not match the value in the header. Most often caused by an incorrect secret or a forged/tampered request.
TIMESTAMP_OUT_OF_TOLERANCEThe timestamp fell outside the configured tolerance window. May indicate clock drift or a replay attack.
Never include the SignatureFailureReason in the HTTP response body sent back to Mercado Pago (or any client). Exposing failure details can help attackers refine forged requests. Log it internally, respond with HTTP 401, and move on.

Complete Spring Boot example

The following example shows a production-ready webhook endpoint that validates the signature, processes the event asynchronously, and always responds HTTP 200 within the timeout window:
import com.mercadopago.exceptions.MPInvalidWebhookSignatureException;
import com.mercadopago.exceptions.SignatureFailureReason;
import com.mercadopago.webhook.WebhookSignatureValidator;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@RestController
@RequestMapping("/webhooks")
public class MercadoPagoWebhookController {

    private static final Logger log = LoggerFactory.getLogger(MercadoPagoWebhookController.class);
    private static final Duration TOLERANCE = Duration.ofMinutes(5);

    private final String webhookSecret = System.getenv("MP_WEBHOOK_SECRET");
    private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

    @PostMapping("/mercadopago")
    public ResponseEntity<Void> receive(
            HttpServletRequest request,
            @RequestBody String body) {

        String xSignature = request.getHeader("x-signature");
        String xRequestId = request.getHeader("x-request-id");
        String dataId     = request.getParameter("data.id");

        // 1. Validate the signature synchronously — fast, no I/O
        try {
            WebhookSignatureValidator.validate(
                xSignature, xRequestId, dataId, webhookSecret, TOLERANCE);
        } catch (MPInvalidWebhookSignatureException e) {
            SignatureFailureReason reason = e.getReason();
            log.warn("Webhook signature invalid: reason={}, requestId={}, ts={}",
                reason, e.getRequestId(), e.getTimestamp());
            return ResponseEntity.status(401).build();
        }

        // 2. Hand off to an async thread — respond HTTP 200 immediately
        executor.submit(() -> processEvent(dataId, body));

        return ResponseEntity.ok().build();
    }

    private void processEvent(String dataId, String body) {
        // Parse body and handle the event type (payment, refund, etc.)
        log.info("Processing webhook for dataId={}", dataId);
        // ... your business logic here
    }
}
Mercado Pago expects your endpoint to respond with HTTP 200 quickly (within a few seconds). If it times out, the notification will be retried. Offload any database writes, downstream API calls, or heavy processing to an async executor or message queue — as shown above.

QR Code notifications

QR Code notifications are not signed by Mercado Pago. Calling WebhookSignatureValidator.validate(...) on a QR Code webhook will always throw MPInvalidWebhookSignatureException with MISSING_SIGNATURE_HEADER or SIGNATURE_MISMATCH. Route QR Code events to a separate, unauthenticated handler and do not call the validator for them.

Security notes

  • Constant-time comparison — the validator uses MessageDigest.isEqual internally, which runs in time proportional only to the length of the strings, not their content. This prevents timing attacks that could otherwise reveal information about the correct signature.
  • Replay protection — enable the Duration tolerance overload in production to reject replayed legitimate notifications.
  • Secret rotation — if you rotate your webhook secret in Tus Integraciones, update your environment variable before the old secret expires. During the rotation window you may receive notifications signed with either key; handle this by trying both secrets and accepting if either passes.

Build docs developers (and LLMs) love