From 1f0102c1333083f7ff0aeb2d6057d28d47c45376 Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Wed, 17 Jun 2026 14:25:36 -0500 Subject: [PATCH 1/2] fix(webhook): preserve data.id case in signature manifest The manifest was lowercasing data.id before computing the HMAC, but MercadoPago signs the notification using the original casing. This caused signature validation to fail whenever data.id contained uppercase letters. --- mercadopago/webhook/validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mercadopago/webhook/validator.py b/mercadopago/webhook/validator.py index 835b569..8edbcda 100644 --- a/mercadopago/webhook/validator.py +++ b/mercadopago/webhook/validator.py @@ -105,7 +105,7 @@ def validate( # pylint: disable=too-many-arguments the manifest before computing the HMAC. data_id: Value of the ``data.id`` query parameter. May be ``None``; in that case the ``id:`` pair is omitted. When present, the - value is lowercased before being included in the manifest. + value is included in the manifest exactly as received. secret: Secret signature configured for the application in Tus Integraciones. tolerance_seconds: Optional maximum allowed drift in seconds @@ -216,7 +216,7 @@ def _build_manifest(data_id, request_id, ts): """Builds the HMAC manifest, omitting empty pairs per the documented rule.""" parts = [] if data_id: - parts.append(f"id:{data_id.lower()}") + parts.append(f"id:{data_id}") if request_id: parts.append(f"request-id:{request_id}") parts.append(f"ts:{ts}") From 3c87120d666f7f63696fde24a224f2c0fb8993ec Mon Sep 17 00:00:00 2001 From: Daniel Alfaro Date: Wed, 17 Jun 2026 14:56:29 -0500 Subject: [PATCH 2/2] test(webhook): update test to reflect case-preserving data.id behavior --- tests/test_webhook_signature_validator.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_webhook_signature_validator.py b/tests/test_webhook_signature_validator.py index ba8b87a..8a3b5d1 100644 --- a/tests/test_webhook_signature_validator.py +++ b/tests/test_webhook_signature_validator.py @@ -28,7 +28,7 @@ def compute_hash(data_id, request_id, ts, secret): parts = [] if data_id: - parts.append(f"id:{data_id.lower()}") + parts.append(f"id:{data_id}") if request_id: parts.append(f"request-id:{request_id}") parts.append(f"ts:{ts}") @@ -52,8 +52,10 @@ def test_happy_path_lowercase(self): WebhookSignatureValidator.validate(VALID_HEADER, REQUEST_ID, DATA_ID_LOWER, SECRET) # --- case 2 --- - def test_uppercase_dataid_is_lowercased(self): - WebhookSignatureValidator.validate(VALID_HEADER, REQUEST_ID, DATA_ID_RAW, SECRET) + def test_uppercase_dataid_is_preserved(self): + upper_hash = compute_hash(DATA_ID_RAW, REQUEST_ID, TS, SECRET) + upper_header = build_header(upper_hash) + WebhookSignatureValidator.validate(upper_header, REQUEST_ID, DATA_ID_RAW, SECRET) # --- case 3 --- def test_malformed_header_raises_malformed(self):