# MD for: https://www.mercadopago.com.uy/developers/es/docs/checkout-pro-orders/payment-notifications.md \# Configure notifications \*\*Webhooks\*\*, also known as \*\*web callbacks\*\*, are HTTP messages that Mercado Pago sends to your server when a specific event occurs in your integration. Instead of your system constantly querying for updates, Webhooks transmit data automatically through an HTTPS POST request. ## Configure Webhooks Below, we present a step-by-step guide to receiving notifications in integrations with Checkout Pro. Once configured, Webhook notifications will be sent whenever any update occurs on the reported topic, including the creation and update of orders and transaction processing. 1\. Go to \[Your integrations\](https://www.mercadopago.com.uy/developers/panel/app) and select the application integrated with Checkout Pro for which you want to activate notifications. !\[configure notifications\](https://www.mercadopago.com.uy/images/cow/not1-select-app-es-v1.png) 2\. In the left menu, select \*\*Webhooks > Configure notifications\*\*. !\[configure notifications\](https://www.mercadopago.com.uy/images/cow/not2-webhooks-es-v1.png) 3\. Select the \*\*Production mode\*\* tab and provide an \`HTTPS URL\` to receive notifications with your productive integration. !\[configure notifications\](https://www.mercadopago.com.uy/images/cow/not3-url-es-v1.png) > NOTE > > If you need to identify multiple accounts, add the parameter \`?client=(sellername)\` to the end of the indicated URL to identify the sellers. 4\. Select the \*\*Order (Mercado Pago)\*\* event to receive notifications, which will be sent in \`JSON\` format via an \`HTTPS POST\` to the URL specified above. !\[configure notifications\](https://www.mercadopago.com.uy/images/cow/orders/notifications-orders-es.png) 5\. Finally, click on \*\*Save configuration\*\*. This will generate an exclusive secret key for the application, which will allow you to validate the authenticity of the received notifications, ensuring that they were sent by Mercado Pago. Keep in mind that this generated key does not have an expiration date and its periodic renewal is not mandatory, although it is recommended. To do this, simply click the \*\*Reset\*\* button. ## Simulate receiving the notification To ensure that notifications are configured correctly, it is necessary to simulate their reception. To do this, follow the steps below. 1\. After configuring the URL and the event, click on \*\*Save configuration\*\*. 2\. Then, click on \*\*Simulate\*\* to test if the indicated URL is correctly receiving notifications. 3\. On the simulation screen, select the URL to be tested. 4\. Next, choose the \*\*event type\*\* and enter the \*\*ID\*\* that will be sent in the notification body (\`Data ID\`). !\[configure notifications\](https://www.mercadopago.com.uy/images/cow/not5-simulate-es.png) 5\. Finally, click on \*\*Send test\*\* to verify the request, the response provided by the server, and the event description. You will receive a response as shown in the example below, representing the \_body\_ of the notification received on your server. \`\`\`json { "action": "order.processed", "api\_version": "v1", "application\_id": "123456", "date\_created": "2025-08-07T18:54:40.851374414Z", "id": "123456", "live\_mode": true, "type": "order", "user\_id": 123456, "data": { "id": "ORD01JYH1Z1YJN4HZ8J3Q0RB3YP6D" } } \`\`\` > WARNING > > If for any reason you are not receiving Mercado Pago notifications, you can alternatively get information about the resource that was not notified by sending a GET to the endpoint :TagComponent{tag="API" text="/v1/orders/{id}" href="/developers/en/reference/online-payments/checkout-pro/get-order/get"}. The use of this method is not recommended, so we additionally suggest contacting \[Support\](https://www.mercadopago.com.uy/developers/en/support). ## Validate the origin of the notification 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 for an \`order\` topic alert. This example includes the complete notification, which contains the \_query params\_, the \`body\`, and the \`header\` of the notification. - \*\*\_Query params\_\*\*: These are query parameters that accompany the URL. In the example, we have \`data.id=ORD01JQ4S4KY8HWQ6NA5PXB65B3D3\` and \`type=order\`. - \*\*\_Body\_\*\*: The body of the notification contains detailed information about the event, such as \`action\`, \`api\_version\`, \`application\_id\`, \`date\_created\`, \`id\`, \`live\_mode\`, \`type\`, \`user\_id\`, and \`data\`. - \*\*\_Header\_\*\*: The header contains important metadata, including the secret signature of the notification \`x-signature\`. \`\`\`http POST /test?data.id=ORD01JQ4S4KY8HWQ6NA5PXB65B3D3&type=order HTTP/1.1 Host: test.requestcatcher.com Accept: \*/\* Accept-Encoding: \* Connection: keep-alive Content-Length: 177 Content-Type: application/json Newrelic: eyJ2IjpbMCwxXSwiZCI6eyJ0eSI6IkFwcCIsImFjIjoiOTg5NTg2IiwiYXAiOiI5NjA2MzYwOTQiLCJ0eCI6ImY4MzljZjg4ODg2MGRmZTIiLCJ0ciI6ImMwOGMwZGMyMjNjZDY2YjJkZWQwMjUxZmYxNWNiNGQ1IiwicHIiOjEuMjUwMzIsInNhIjp0cnVlLCJ0aSI6MTc0Mjg0MjU4MDE2NCwiaWQiOiIxOGI2NDcxNjNkNzI3NjU4IiwidGsiOiIxNzA5NzA3In19= Traceparent: 00-c08c0dc223cd66b2ded0251ff15cb4d5-18b647163d727658-01 Tracestate: 1709707@nr=0-0-989586-960636094-18b647163d727658-f839cf888860dfe2-1-1.250320-1742842580164 User-Agent: restclient-node/4.15.3 X-Request-Id: 2066ca19-c6f1-498a-be75-1923005edd06 X-Rest-Pool-Name: /services/webhooks.js X-Retry: 0 X-Signature: ts=1742505638683,v1=ced36ab6d33566bb1e16c125819b8d840d6b8ef136b0b9127c76064466f5229b X-Socket-Timeout: 22000 {"action":"order.processed","api\_version":"v1","application\_id":"123456","date\_created":"2025-08-07T18:54:40.851374414Z","id":"123456","live\_mode":true,"type":"order","user\_id":123456,"data":{"id":"ORD01JYH1Z1YJN4HZ8J3Q0RB3YP6D"}} \`\`\` From the received Webhook notification, you can validate the authenticity of its origin. Mercado Pago will always include the secret key in Webhook notifications, which will allow you to validate their authenticity. This key will be sent in the \`x-signature\` \_header\_, which will look similar to the example below. \`\`\`plain ts=1742505638683,v1=ced36ab6d33566bb1e16c125819b8d840d6b8ef136b0b9127c76064466f5229b \`\`\` To confirm the validation, you need to extract the key contained in the \_header\_ and compare it with the key provided for your application in \[Your integrations\](https://www.mercadopago.com.uy/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 notification received. To obtain your secret key (\`secret\`), select the application in \[Your integrations\](https://www.mercadopago.com.uy/developers/panel/app), click on \*\*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 \`data.id\` is returned with uppercase alphanumeric characters, convert it to lowercase before using it in the manifest. For example, \`ORD01JQ4S4KY8HWQ6NA5PXB65B3D3\` must be used as \`ord01jq4s4ky8hwq6na5pxb65b3d3\`. - \`\[x-request-id\_header\]\` must be replaced by the value received in the \`x-request-id\` \_header\_. - \`\[ts\_header\]\` will be the \`ts\` value extracted from the \`x-signature\` \_header\_. > 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 calculating the \`HMAC\`. 3\. In \[Your integrations\](https://www.mercadopago.com.uy/developers/panel/app), select the integrated application, click on \*\*Webhooks > Configure notification\*\* and reveal the generated secret key. !\[configure notifications\](https://www.mercadopago.com.uy/images/api-orders/not6-signature-es-v1.png) 4\. Generate the counter-key for validation. To do this, calculate 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 that they match exactly. You can also use the \_timestamp\_ extracted from the \_header\_ to compare it with a \_timestamp\_ generated at the time of notification reception, in order to establish a delay tolerance for message reception. See complete code examples below: * [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 :::: ::::: ## Required actions after receiving the notification When you receive a notification on your platform, Mercado Pago expects a response to validate that the reception was correct. To do this, you must return an \`HTTP STATUS 200 (OK)\` or \`201 (CREATED)\`. The \*\*waiting time\*\* for this confirmation will be \*\*22 seconds\*\*. If this confirmation is not sent, the system will understand that the notification was not received and will make a new sending attempt \*\*every 15 minutes\*\*, until it receives the response. After the third attempt, the deadline will be extended, but the submissions will continue. sequenceDiagram participant MercadoPago as Mercado Pago participant Integrador as Integrator MercadoPago->>Integrador: attempt: 1. Delay: 0 minutes MercadoPago->>Integrador: attempt: 2. Delay: 15 minutes MercadoPago->>Integrador: attempt: 3. Delay: 30 minutes MercadoPago->>Integrador: attempt: 4. Delay: 6 hours MercadoPago->>Integrador: attempt: 5. Delay: 48 hours MercadoPago->>Integrador: attempt: 6. Delay: 96 hours MercadoPago->>Integrador: attempt: 7. Delay: 96 hours MercadoPago->>Integrador: attempt: 8. Delay: 96 hours After responding to the notification and confirming its receipt, you can get all the information about the notified order by sending a GET to the endpoint :TagComponent{tag="API" text="/v1/orders/{id}" href="/developers/en/reference/online-payments/checkout-pro/get-order/get"}. With that information, you can make the necessary updates to your platform, such as updating an approved payment.