# MD for: https://www.mercadopago.com.ar/developers/es/docs/qr-code/optional-notifications.md \# Configure optional notifications In addition to the primary orders topic, Mercado Pago offers optional topics that notify your application about integration management events: account linking via OAuth, buyer-initiated claims, and chargebacks. These notifications complement the main flow and keep your system synchronized with events beyond payment processing. None of these topics replaces the orders topic or is mandatory. Activate only the ones relevant to your integration. ## Available topics | Topic | Panel name | Description | |---|---|---| | \`mp-connect\` | Application linking | Notifies when an account is linked or unlinked via \[OAuth\](https://www.mercadopago.com.ar/developers/en/docs/security/oauth). Applies to all integrations that use OAuth. | | \`topic\_claims\_integration\_wh\` | Claims | Notifies when a buyer files a claim or when a status change occurs. | | \`topic\_chargebacks\_wh\` | Chargebacks | Notifies when a buyer initiates a chargeback or a status change occurs. | ## Configure Webhooks Follow the steps below to activate the optional topics in your integration. > RED\_MESSAGE > > If you are developing using test credentials, go to \*\*Your integrations > Application details > Test credentials > Test credentials data\*\*, sign in to \[Mercado Pago Developers\](https://www.mercadopago.com.ar/developers/en/docs) with the username and password of that test account, and configure the Webhooks in production mode for that account. This way, you will be able to test the notifications correctly. 1\. Go to \[Your integrations\](https://www.mercadopago.com/developers/panel/app) and select the application for which you want to activate notifications. !\[configure notifications\](https://www.mercadopago.com.ar/images/api-orders/not1-app-es-v1.png) 2\. In the left menu, select \*\*Webhooks > Configure notifications\*\*. !\[configure notifications\](https://www.mercadopago.com.ar/images/api-orders/not2-configure-es-v1.png) 3\. Select the \*\*Production mode\*\* tab and provide an \`HTTPS URL\` to receive notifications. 4\. In the optional topics section, select the events you want to activate: \*\*Application linking\*\*, \*\*Claims\*\*, and/or \*\*Chargebacks\*\*. 5\. Finally, click \*\*Save configuration\*\*. This will generate a :toolTipComponent\[secret key\]{content="Key used to validate the authenticity of each received notification." link="/developers/en/docs/your-integrations/notifications/webhooks"} for your application. Note that this key has no expiration date and periodic renewal is not mandatory, though recommended. To do so, click the \*\*Reset\*\* button. ## Simulate receiving the notification To ensure notifications are configured correctly, simulate receiving them by following the steps below. 1\. After configuring your Webhooks, click \*\*Simulate notification\*\*. 2\. On the simulation screen, select the URL to test. 3\. Choose the event type you want to test and enter the \*\*resource ID\*\* to be sent in the notification body (\`Data ID\`). !\[cofigure notifications\](https://www.mercadopago.com.ar/images/api-orders/not5-order-es-v2.png) 4\. Finally, click \*\*Send test\*\* to verify the request, the server response, and the event description. ## Validate the notification origin Validating the origin of a notification is essential to ensure the security and authenticity of the received information. This process helps prevent fraud and ensures that only legitimate notifications are processed. Mercado Pago will send your server a notification similar to the example below. Below are examples of notifications for each topic. ::::TabsComponent :::TabComponent{title="Application linking"} \`\`\` POST /?data.id=123456789&type=mp-connect HTTP/1.1 Host: test-optional-nots.requestcatcher.com Accept: \*/\* Accept-Encoding: \* Connection: keep-alive Content-Length: 187 Content-Type: application/json User-Agent: restclient-node/5.1.10 X-Request-Id: 4ed4fa2b-0b31-42ec-a62f-ad793c486c59 X-Rest-Pool-Name: /services/webhooks.js X-Retry: 0 X-Signature: ts=1781009491,v1=654866c48793d9f716f255f8a8e6cb162f643d93b29391daa6ac7ce78cf0ce81 X-Socket-Timeout: 22000 {"action":"application.authorized","api\_version":"v1","data":{"id":"123456789"},"date\_created":"2026-06-12T13:14:01.351Z","id":100000000000,"live\_mode":true,"type":"mp-connect","user\_id":123456789} \`\`\` | Field | Type | Description | |---|---|---| | \`action\` | string | Notified event. Possible values: \`application.authorized\` (linking) and \`application.deauthorized\` (unlinking). | | \`api\_version\` | string | API version. Always \`v1\`. | | \`data.id\` | string | Identifier of the resource associated with the event. | | \`date\_created\` | string | Notification creation date (ISO 8601). | | \`id\` | long | Unique notification identifier. Use it for idempotency control. | | \`live\_mode\` | boolean | \`true\` in production, \`false\` in test mode. | | \`type\` | string | Always \`mp-connect\`. | | \`user\_id\` | long | Identifier of the seller for whom the notification is sent. | ::: :::TabComponent{title="Claims"} \`\`\` POST /?data.id=1234567890&type=claim HTTP/1.1 Host: test-optional-nots.requestcatcher.com Accept: \*/\* Accept-Encoding: \* Connection: keep-alive Content-Length: 207 Content-Type: application/json User-Agent: restclient-node/5.1.10 X-Request-Id: 1d07590a-fd51-4e67-8b13-7d45600670e5 X-Rest-Pool-Name: /services/webhooks.js X-Retry: 0 X-Signature: ts=1781009658,v1=e446ded9a9e0dd94acba3fd0fa9e35e497abcf2ef11a6d0f5291f7c7eab00fb7 X-Socket-Timeout: 22000 {"action":"updated","api\_version":"v1","data":{"id":1234567890,"resource":"/v1/claims/1234567890"},"date\_created":"2026-06-12T06:14:40-04:00","id":"00000000-0000-0000-0000-000000000001","live\_mode":true,"type":"claim","user\_id":123456789} \`\`\` | Field | Type | Description | |---|---|---| | \`action\` | string | Notified event. Indicates the action that occurred on the claim. | | \`api\_version\` | string | API version. Always \`v1\`. | | \`data.id\` | long | Unique identifier of the claim. | | \`data.resource\` | string | Path of the claim resource in the API. | | \`date\_created\` | string | Notification creation date (ISO 8601). | | \`id\` | string | Unique notification identifier (UUID). Use it for idempotency control. | | \`live\_mode\` | boolean | \`true\` in production, \`false\` in test mode. | | \`type\` | string | Always \`claim\`. | | \`user\_id\` | long | Identifier of the seller for whom the notification is sent. | ::: :::TabComponent{title="Chargebacks"} \`\`\` POST /?data.id=114544942708&type=topic\_chargebacks\_wh HTTP/1.1 Host: test-optional-nots.requestcatcher.com Accept: \*/\* Accept-Encoding: \* Connection: keep-alive Content-Type: application/json User-Agent: restclient-node/5.1.10 X-Request-Id: 948ac0a7-bf77-4877-8fe8-8ab69a356fa4 X-Rest-Pool-Name: /services/webhooks.js X-Retry: 0 X-Signature: ts=1781009697,v1=108c6756aa1b96a5303b41769bb89261ebeb2922333b483886502f5b7ae3d79d X-Socket-Timeout: 22000 {"actions":\["changed\_case\_status"\],"api\_version":"v1","application\_id":1234567890987654,"data":{"checkout":"PRO","date\_updated":"0001-01-01T00:00:00Z","id":217000061307271000,"payment\_id":81034165129,"product\_id":"BC32A57TRPP001U8NHHG","site\_id":"MLA","transaction\_intent\_id":""},"date\_created":"2024-07-02T22:03:24-04:00","id":114544942708,"live\_mode":true,"type":"topic\_chargebacks\_wh","user\_id":123456789,"version":1720427447} \`\`\` | Field | Type | Description | |---|---|---| | \`actions\` | array | List of actions that occurred. For example, \`changed\_case\_status\`. | | \`api\_version\` | string | API version. Always \`v1\`. | | \`data.id\` | long | Chargeback identifier. | | \`data.payment\_id\` | long | Associated payment identifier. | | \`data.site\_id\` | string | Site identifier (country). | | \`date\_created\` | string | Notification creation date (ISO 8601). | | \`id\` | long | Notification identifier. | | \`live\_mode\` | boolean | \`true\` in production, \`false\` in test mode. | | \`type\` | string | Always \`topic\_chargebacks\_wh\`. | | \`user\_id\` | long | Seller identifier. | | \`version\` | long | Event version timestamp. | ::: :::: From the received notification, you can validate the authenticity of its origin through the secret key. This key will be sent in the \`x-signature\` \_header\_, with the following format: \`\`\` ts=1742505638683,v1=ced36ab6d33566bb1e16c125819b8d840d6b8ef136b0b9127c76064466f5229b \`\`\` To confirm the validation, extract the key from the \_header\_ and compare it with the key provided for your application in \[Your integrations\](https://www.mercadopago.com.ar/developers/panel/app). To confirm the validation, it is necessary to extract the key from the \_header\_ and compare it with the key provided for your application in \[Your integrations\](https://www.mercadopago.com.ar/developers/panel/app). Follow one of the approaches below to validate the authenticity of the notification. ::::TabsComponent :::TabComponent{title="With SDKs"} The official SDK implements HMAC-based Webhook Signature Verification to authenticate the origin of each received notification. To get your secret key (\`secret\`), go to \[Your integrations\](https://www.mercadopago.com.ar/developers/panel/app), select the application, click \*\*Webhooks > Configure notification\*\* and reveal the generated key. * [csharp ](#editor%5F5) * [go ](#editor%5F4) * [java ](#editor%5F6) * [javascript ](#editor%5F2) * [php ](#editor%5F1) * [python ](#editor%5F3) * [ruby ](#editor%5F7) php javascript python go csharp java ruby ``` NOTE > > If any of the values (\`data.id\`, \`x-request-id\`) are not present in the received notification, you must remove them from the manifest before computing the \`HMAC\`. 3\. In \[Your integrations\](https://www.mercadopago.com.ar/developers/panel/app), select the integrated application, click \*\*Webhooks > Configure notification\*\* and reveal the generated secret key. 4\. Generate the counter-key for validation. To do this, compute an \[HMAC\](https://en.wikipedia.org/wiki/HMAC) with the \`SHA256 hash\` function in hexadecimal base, using the secret key as the key and the \_template\_ with the values as the message. * [java ](#editor%5F10) * [node ](#editor%5F9) * [php ](#editor%5F8) * [python ](#editor%5F11) php node java python ``` $cyphedSignature = hash_hmac('sha256', $data, $key); ``` Copiar ``` const crypto = require('crypto'); const cyphedSignature = crypto .createHmac('sha256', secret) .update(signatureTemplateParsed) .digest('hex'); ``` Copiar ``` String cyphedSignature = new HmacUtils("HmacSHA256", secret).hmacHex(signedTemplate); ``` Copiar ``` import hashlib, hmac, binascii cyphedSignature = binascii.hexlify(hmac.new(secret.encode(), signedTemplate.encode(), hashlib.sha256).digest()) ``` Copiar 5\. Finally, compare the generated key with the key extracted from the \_header\_, ensuring they match exactly. Additionally, you can use the \_timestamp\_ extracted from the \_header\_ to compare it with a \_timestamp\_ generated at the time of receipt, in order to establish a delay tolerance in receiving the message. Here are complete code examples: * [csharp ](#editor%5F16) * [go ](#editor%5F15) * [java ](#editor%5F17) * [javascript ](#editor%5F13) * [php ](#editor%5F12) * [python ](#editor%5F14) * [ruby ](#editor%5F18) php javascript python go csharp java ruby ``` (); if (!string.IsNullOrEmpty(dataId)) parts.Add(\[\[\[ \`\`\`php (); if (!dataId.isEmpty()) parts.add("id:" + dataId); if (!xRequestId.isEmpty()) parts.add("request-id:" + xRequestId); parts.add("ts:" + ts); String manifest = String.join(";", parts) + ";"; Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF\_8), "HmacSHA256")); byte\[\] bytes = mac.doFinal(manifest.getBytes(StandardCharsets.UTF\_8)); StringBuilder sb = new StringBuilder(); for (byte b : bytes) sb.append(String.format("%02x", b & 0xff)); String computed = sb.toString(); if (!MessageDigest.isEqual( computed.getBytes(StandardCharsets.UTF\_8), hash.getBytes(StandardCharsets.UTF\_8))) { response.setStatus(401); return; } response.setStatus(200); \`\`\` \`\`\`ruby require 'openssl' x\_signature = request.headers\['x-signature'\] || '' x\_request\_id = request.headers\['x-request-id'\] || '' data\_id = (params\['data.id'\] || '').downcase ts = hash\_value = nil x\_signature.split(',').each do |part| key, value = part.split('=', 2) next unless key && value ts = value.strip if key.strip == 'ts' hash\_value = value.strip if key.strip == 'v1' end parts = \[\] parts << "id:#{data\_id}" unless data\_id.empty? parts << "request-id:#{x\_request\_id}" unless x\_request\_id.empty? parts << "ts:#{ts}" manifest = "#{parts.join(';')};" computed = OpenSSL::HMAC.hexdigest('SHA256', secret, manifest) unless OpenSSL.fixed\_length\_secure\_compare(computed, hash\_value) head :unauthorized return end head :ok \`\`\` \]\]\]quot;id:{dataId}"); if (!string.IsNullOrEmpty(xRequestId)) parts.Add(\[\[\[ \`\`\`php (); if (!dataId.isEmpty()) parts.add("id:" + dataId); if (!xRequestId.isEmpty()) parts.add("request-id:" + xRequestId); parts.add("ts:" + ts); String manifest = String.join(";", parts) + ";"; Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF\_8), "HmacSHA256")); byte\[\] bytes = mac.doFinal(manifest.getBytes(StandardCharsets.UTF\_8)); StringBuilder sb = new StringBuilder(); for (byte b : bytes) sb.append(String.format("%02x", b & 0xff)); String computed = sb.toString(); if (!MessageDigest.isEqual( computed.getBytes(StandardCharsets.UTF\_8), hash.getBytes(StandardCharsets.UTF\_8))) { response.setStatus(401); return; } response.setStatus(200); \`\`\` \`\`\`ruby require 'openssl' x\_signature = request.headers\['x-signature'\] || '' x\_request\_id = request.headers\['x-request-id'\] || '' data\_id = (params\['data.id'\] || '').downcase ts = hash\_value = nil x\_signature.split(',').each do |part| key, value = part.split('=', 2) next unless key && value ts = value.strip if key.strip == 'ts' hash\_value = value.strip if key.strip == 'v1' end parts = \[\] parts << "id:#{data\_id}" unless data\_id.empty? parts << "request-id:#{x\_request\_id}" unless x\_request\_id.empty? parts << "ts:#{ts}" manifest = "#{parts.join(';')};" computed = OpenSSL::HMAC.hexdigest('SHA256', secret, manifest) unless OpenSSL.fixed\_length\_secure\_compare(computed, hash\_value) head :unauthorized return end head :ok \`\`\` \]\]\]quot;request-id:{xRequestId}"); parts.Add(\[\[\[ \`\`\`php (); if (!dataId.isEmpty()) parts.add("id:" + dataId); if (!xRequestId.isEmpty()) parts.add("request-id:" + xRequestId); parts.add("ts:" + ts); String manifest = String.join(";", parts) + ";"; Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF\_8), "HmacSHA256")); byte\[\] bytes = mac.doFinal(manifest.getBytes(StandardCharsets.UTF\_8)); StringBuilder sb = new StringBuilder(); for (byte b : bytes) sb.append(String.format("%02x", b & 0xff)); String computed = sb.toString(); if (!MessageDigest.isEqual( computed.getBytes(StandardCharsets.UTF\_8), hash.getBytes(StandardCharsets.UTF\_8))) { response.setStatus(401); return; } response.setStatus(200); \`\`\` \`\`\`ruby require 'openssl' x\_signature = request.headers\['x-signature'\] || '' x\_request\_id = request.headers\['x-request-id'\] || '' data\_id = (params\['data.id'\] || '').downcase ts = hash\_value = nil x\_signature.split(',').each do |part| key, value = part.split('=', 2) next unless key && value ts = value.strip if key.strip == 'ts' hash\_value = value.strip if key.strip == 'v1' end parts = \[\] parts << "id:#{data\_id}" unless data\_id.empty? parts << "request-id:#{x\_request\_id}" unless x\_request\_id.empty? parts << "ts:#{ts}" manifest = "#{parts.join(';')};" computed = OpenSSL::HMAC.hexdigest('SHA256', secret, manifest) unless OpenSSL.fixed\_length\_secure\_compare(computed, hash\_value) head :unauthorized return end head :ok \`\`\` \]\]\]quot;ts:{ts}"); var manifest = string.Join(";", parts) + ";"; using var hmacSha = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); var computed = BitConverter .ToString(hmacSha.ComputeHash(Encoding.UTF8.GetBytes(manifest))) .Replace("-", "").ToLowerInvariant(); if (!CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(computed), Encoding.UTF8.GetBytes(hash))) { return Unauthorized(); } return Ok(); Copiar ``` import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.ArrayList; import java.util.List; String xSignature = request.getHeader("x-signature") != null ? request.getHeader("x-signature") : ""; String xRequestId = request.getHeader("x-request-id") != null ? request.getHeader("x-request-id") : ""; String dataId = request.getParameter("data.id") != null ? request.getParameter("data.id").toLowerCase() : ""; String ts = null, hash = null; for (String part : xSignature.split(",")) { String[] kv = part.split("=", 2); if (kv.length != 2) continue; String key = kv[0].trim(); String val = kv[1].trim(); if ("ts".equals(key)) ts = val; if ("v1".equals(key)) hash = val; } List parts = new ArrayList<>(); if (!dataId.isEmpty()) parts.add("id:" + dataId); if (!xRequestId.isEmpty()) parts.add("request-id:" + xRequestId); parts.add("ts:" + ts); String manifest = String.join(";", parts) + ";"; Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); byte[] bytes = mac.doFinal(manifest.getBytes(StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); for (byte b : bytes) sb.append(String.format("%02x", b & 0xff)); String computed = sb.toString(); if (!MessageDigest.isEqual( computed.getBytes(StandardCharsets.UTF_8), hash.getBytes(StandardCharsets.UTF_8))) { response.setStatus(401); return; } response.setStatus(200); ``` Copiar ``` require 'openssl' x_signature = request.headers['x-signature'] || '' x_request_id = request.headers['x-request-id'] || '' data_id = (params['data.id'] || '').downcase ts = hash_value = nil x_signature.split(',').each do |part| key, value = part.split('=', 2) next unless key && value ts = value.strip if key.strip == 'ts' hash_value = value.strip if key.strip == 'v1' end parts = [] parts << "id:#{data_id}" unless data_id.empty? parts << "request-id:#{x_request_id}" unless x_request_id.empty? parts << "ts:#{ts}" manifest = "#{parts.join(';')};" computed = OpenSSL::HMAC.hexdigest('SHA256', secret, manifest) unless OpenSSL.fixed_length_secure_compare(computed, hash_value) head :unauthorized return end head :ok ``` Copiar ::: :::: ## Actions needed after receiving the notification When you receive a notification on your platform, Mercado Pago expects a response to validate that the receipt was correct. To do so, return an \`HTTP STATUS 200\` or \`201\` within 22 seconds of receipt. We recommend that you first respond with a \`200\` or \`201\`, and then process the notification on the server, to avoid duplicate notifications. If this response is not sent, the system will make new delivery attempts every 15 minutes. After the first failures, the interval progressively increases, but deliveries continue until the notification is confirmed.