Autenticación Webhooks
Es importante verificar la integridad de todas las notificaciones que Conekta te envía por webhook para asegurar que son legítimos y que no han sido modificados. El mecanismo para hacer esta verificación involucra un juego de llaves RSA público/privado para tu compañía para firmar un sha256 del cuerpo de la petición.
Inicialización - Solo Una Vez
El primer paso es crear un juego de llaves RSA en Conekta, vas a recibir la llave pública para verificar mensajes y Conekta se va a quedar con la llave privada para firmar mensajes. Solo es necesario realizar este proceso una vez. Puedes iniciar llaves de webhook con un curl así:
curl --request POST \
--url https://api.conekta.io/webhook_keys \
--header 'accept: application/vnd.conekta-v2.0.0+json' \
-u key_eYvWV7gSDkNYXsmr: \
--header 'content-type: application/json' \
--data-raw '{
"active": true
}'
Lo cuál va retornar el siguiente mensaje
{
"active": true,
"livemode": false,
"created_at": 1651706790,
"id": "62730ba6fb7dfd6a712f118e",
"object": "webhook_key",
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0iz57mpVAvxQtuxOyWsW\nhM1Jai7WB5cNZFs8xK53A9X9LQiXz30pzoFIhVo9Zm5K5GBpb9frCH8w6yr+/xrX\n0VUjbp1VTgZ2iGOm83ykLN7YYQJk5pCt/B69eFPYbMCKFzvauwTtN9tf2KcLQQ2y\nSohxd3H51uUIGcxnSR5oVPoCdY4geSWfK0/FE4SAyVsTB/b3mS0KUor7R2tZupKm\nrS26O6QFQrk0ELuGIIriJimjxaQG9V7E/TumKkbDPAcJsiZBF8oep02sXbdNpaxl\nj5PNkVIQ2F09BfDJl71DrcAIKYXG7HSgDEoiRkZ3jIzudUNA+qkpYwHJ5Qx9qmgy\nuQIDAQAB\n-----END PUBLIC KEY-----\n"
}
Puedes ver más detalle en la sección de en la referencia de llaves para webhooks.
Verificación de Mensajes - En Cada Mensaje
Puedes validar la integridad del mensaje consumiendo el header de 'DIGEST' y comprobando que representa el sha256 del mensaje cifrado por la llave privada. Como nota, es esperado que la codificación del payload está en UTF-8.
{"data":{"object":{"id":"61fdc53b0211a6764e57ec4f","livemode":true,"created_at":1644021051,"currency":"MXN","channel":{"segment":"Checkout","checkout_request_id":"4ed49de7-60e7-4e87-a0de-48b433f3ea2f","checkout_request_type":"PaymentLink","id":"channel_2rFeDewRPQpn3NFoe"},"payment_method":{"name":"OscarNava","exp_month":"12","exp_year":"22","object":"card_payment","type":"credit","last4":"0002","brand":"visa","issuer":"banamex","account_type":"BANAMEX","country":"MX","fraud_indicators":[]},"object":"charge","description":"Payment from order","status":"declined","amount":10000,"fee":708,"customer_id":"cus_2rFeDEaPCq2GkhLS8","order_id":"ord_2rFeDeCbsDG8R6bK4"},"previous_attributes":{}},"livemode":true,"webhook_status":"failing","webhook_logs":[{"id":"webhl_2rFeDewRPQpn3NFoj","url":"https://www.example.com/conekta/webhooks","failed_attempts":5,"last_http_response_status":598,"object":"webhook_log","last_attempted_at":0}],"id":"61fdc53b0211a6764e57ec53","object":"event","type":"charge.created","created_at":1644021051}
Digest ejemplo:
PY0lEriF5Tt4T0ZVqwEQtS5+skbqUpCWwwDs5EtBEX5vriK15KuLCuTndttDR4jvlSvDsDVSUOCxXrpxMJ0x0cePCa79xGa23r/hcv4CPcfr2UMe4IKJuJSlJc3XCfOQB1rfk6fQCMr7AbiVtvacr91yYxg7QoJq2/Y0YRo7RreT61X7/dmGhgzFrG0TALQ2R0PbQyAXIvO7l5+00Yncdc0IeyCBTL3/Wa0zN+Dc4UZW3iWFpNOIyljDKxmBp+2D0DxPeLfXw7fnVskQMSCM7rCVXSoP7k8BCyIDRZ62QieMXW5CubixFaPFrI1f6K8gh67lguYF2XSY/bYb7IU4Jg==
Puedes comprobar la integridad del mensaje con el siguiente código
#decode base64
cat DIGEST.sha256.base64 | base64 --decode > signature.sha256
#Verify digest
openssl dgst -sha256 -verify pubkey.pem -signature signature.sha256 notification_payload.utf8.json
#Verified OK
require 'openssl'
require 'base64'
pubkey = OpenSSL::PKey::RSA.new("-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0iz57mpVAvxQtuxOyWsW\nhM1Jai7WB5cNZFs8xK53A9X9LQiXz30pzoFIhVo9Zm5K5GBpb9frCH8w6yr+/xrX\n0VUjbp1VTgZ2iGOm83ykLN7YYQJk5pCt/B69eFPYbMCKFzvauwTtN9tf2KcLQQ2y\nSohxd3H51uUIGcxnSR5oVPoCdY4geSWfK0/FE4SAyVsTB/b3mS0KUor7R2tZupKm\nrS26O6QFQrk0ELuGIIriJimjxaQG9V7E/TumKkbDPAcJsiZBF8oep02sXbdNpaxl\nj5PNkVIQ2F09BfDJl71DrcAIKYXG7HSgDEoiRkZ3jIzudUNA+qkpYwHJ5Qx9qmgy\nuQIDAQAB\n-----END PUBLIC KEY-----\n")
payload = '{"data":{"object":{"id":"61fdc53b0211a6764e57ec4f","livemode":true,"created_at":1644021051,"currency":"MXN","channel":{"segment":"Checkout","checkout_request_id":"4ed49de7-60e7-4e87-a0de-48b433f3ea2f","checkout_request_type":"PaymentLink","id":"channel_2rFeDewRPQpn3NFoe"},"payment_method":{"name":"OscarNava","exp_month":"12","exp_year":"22","object":"card_payment","type":"credit","last4":"0002","brand":"visa","issuer":"banamex","account_type":"BANAMEX","country":"MX","fraud_indicators":[]},"object":"charge","description":"Payment from order","status":"declined","amount":10000,"fee":708,"customer_id":"cus_2rFeDEaPCq2GkhLS8","order_id":"ord_2rFeDeCbsDG8R6bK4"},"previous_attributes":{}},"livemode":true,"webhook_status":"failing","webhook_logs":[{"id":"webhl_2rFeDewRPQpn3NFoj","url":"https://www.example.com/conekta/webhooks","failed_attempts":5,"last_http_response_status":598,"object":"webhook_log","last_attempted_at":0}],"id":"61fdc53b0211a6764e57ec53","object":"event","type":"charge.created","created_at":1644021051}'
signature = 'PY0lEriF5Tt4T0ZVqwEQtS5+skbqUpCWwwDs5EtBEX5vriK15KuLCuTndttDR4jvlSvDsDVSUOCxXrpxMJ0x0cePCa79xGa23r/hcv4CPcfr2UMe4IKJuJSlJc3XCfOQB1rfk6fQCMr7AbiVtvacr91yYxg7QoJq2/Y0YRo7RreT61X7/dmGhgzFrG0TALQ2R0PbQyAXIvO7l5+00Yncdc0IeyCBTL3/Wa0zN+Dc4UZW3iWFpNOIyljDKxmBp+2D0DxPeLfXw7fnVskQMSCM7rCVXSoP7k8BCyIDRZ62QieMXW5CubixFaPFrI1f6K8gh67lguYF2XSY/bYb7IU4Jg=='
if pubkey.verify(OpenSSL::Digest.new('SHA256'), Base64.decode64(signature), payload)
puts "Verification succeeded!"
end
package org.conekta;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws Exception {
String message = "{\"data\":{\"object\":{\"id\":\"61fdc53b0211a6764e57ec4f\",\"livemode\":true,\"created_at\":1644021051,\"currency\":\"MXN\",\"channel\":{\"segment\":\"Checkout\",\"checkout_request_id\":\"4ed49de7-60e7-4e87-a0de-48b433f3ea2f\",\"checkout_request_type\":\"PaymentLink\",\"id\":\"channel_2rFeDewRPQpn3NFoe\"},\"payment_method\":{\"name\":\"OscarNava\",\"exp_month\":\"12\",\"exp_year\":\"22\",\"object\":\"card_payment\",\"type\":\"credit\",\"last4\":\"0002\",\"brand\":\"visa\",\"issuer\":\"banamex\",\"account_type\":\"BANAMEX\",\"country\":\"MX\",\"fraud_indicators\":[]},\"object\":\"charge\",\"description\":\"Payment from order\",\"status\":\"declined\",\"amount\":10000,\"fee\":708,\"customer_id\":\"cus_2rFeDEaPCq2GkhLS8\",\"order_id\":\"ord_2rFeDeCbsDG8R6bK4\"},\"previous_attributes\":{}},\"livemode\":true,\"webhook_status\":\"failing\",\"webhook_logs\":[{\"id\":\"webhl_2rFeDewRPQpn3NFoj\",\"url\":\"https://www.example.com/conekta/webhooks\",\"failed_attempts\":5,\"last_http_response_status\":598,\"object\":\"webhook_log\",\"last_attempted_at\":0}],\"id\":\"61fdc53b0211a6764e57ec53\",\"object\":\"event\",\"type\":\"charge.created\",\"created_at\":1644021051}";
byte[] signature = "PY0lEriF5Tt4T0ZVqwEQtS5+skbqUpCWwwDs5EtBEX5vriK15KuLCuTndttDR4jvlSvDsDVSUOCxXrpxMJ0x0cePCa79xGa23r/hcv4CPcfr2UMe4IKJuJSlJc3XCfOQB1rfk6fQCMr7AbiVtvacr91yYxg7QoJq2/Y0YRo7RreT61X7/dmGhgzFrG0TALQ2R0PbQyAXIvO7l5+00Yncdc0IeyCBTL3/Wa0zN+Dc4UZW3iWFpNOIyljDKxmBp+2D0DxPeLfXw7fnVskQMSCM7rCVXSoP7k8BCyIDRZ62QieMXW5CubixFaPFrI1f6K8gh67lguYF2XSY/bYb7IU4Jg==".getBytes();
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
String bytekey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0iz57mpVAvxQtuxOyWsW\nhM1Jai7WB5cNZFs8xK53A9X9LQiXz30pzoFIhVo9Zm5K5GBpb9frCH8w6yr+/xrX\n0VUjbp1VTgZ2iGOm83ykLN7YYQJk5pCt/B69eFPYbMCKFzvauwTtN9tf2KcLQQ2y\nSohxd3H51uUIGcxnSR5oVPoCdY4geSWfK0/FE4SAyVsTB/b3mS0KUor7R2tZupKm\nrS26O6QFQrk0ELuGIIriJimjxaQG9V7E/TumKkbDPAcJsiZBF8oep02sXbdNpaxl\nj5PNkVIQ2F09BfDJl71DrcAIKYXG7HSgDEoiRkZ3jIzudUNA+qkpYwHJ5Qx9qmgy\nuQIDAQAB\n-----END PUBLIC KEY-----\n";
PublicKey key = load(bytekey);
Signature sig = Signature.getInstance("SHA256WithRSA");
sig.initVerify(key);
sig.update(messageBytes);
boolean result = sig.verify(Base64.getDecoder().decode(signature));
// result
System.out.println("Message = " + message);
System.out.println("Signature = "
+ Base64.getDecoder().decode(signature));
System.out.println("Verification Result = " + result);
}
public static PublicKey load(String publicKeyContent) throws Exception {
publicKeyContent = publicKeyContent.
replaceAll("\\n", "").
replace("-----BEGIN PUBLIC KEY-----", "").
replace("-----END PUBLIC KEY-----", "");
;
X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyContent));
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(X509publicKey);
}
}
const NodeRSA = require('node-rsa');
const publicKey = new NodeRSA("-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0iz57mpVAvxQtuxOyWsW\nhM1Jai7WB5cNZFs8xK53A9X9LQiXz30pzoFIhVo9Zm5K5GBpb9frCH8w6yr+/xrX\n0VUjbp1VTgZ2iGOm83ykLN7YYQJk5pCt/B69eFPYbMCKFzvauwTtN9tf2KcLQQ2y\nSohxd3H51uUIGcxnSR5oVPoCdY4geSWfK0/FE4SAyVsTB/b3mS0KUor7R2tZupKm\nrS26O6QFQrk0ELuGIIriJimjxaQG9V7E/TumKkbDPAcJsiZBF8oep02sXbdNpaxl\nj5PNkVIQ2F09BfDJl71DrcAIKYXG7HSgDEoiRkZ3jIzudUNA+qkpYwHJ5Qx9qmgy\nuQIDAQAB\n-----END PUBLIC KEY-----\n");
const signature = 'PY0lEriF5Tt4T0ZVqwEQtS5+skbqUpCWwwDs5EtBEX5vriK15KuLCuTndttDR4jvlSvDsDVSUOCxXrpxMJ0x0cePCa79xGa23r/hcv4CPcfr2UMe4IKJuJSlJc3XCfOQB1rfk6fQCMr7AbiVtvacr91yYxg7QoJq2/Y0YRo7RreT61X7/dmGhgzFrG0TALQ2R0PbQyAXIvO7l5+00Yncdc0IeyCBTL3/Wa0zN+Dc4UZW3iWFpNOIyljDKxmBp+2D0DxPeLfXw7fnVskQMSCM7rCVXSoP7k8BCyIDRZ62QieMXW5CubixFaPFrI1f6K8gh67lguYF2XSY/bYb7IU4Jg=='
const payload = '{"data":{"object":{"id":"61fdc53b0211a6764e57ec4f","livemode":true,"created_at":1644021051,"currency":"MXN","channel":{"segment":"Checkout","checkout_request_id":"4ed49de7-60e7-4e87-a0de-48b433f3ea2f","checkout_request_type":"PaymentLink","id":"channel_2rFeDewRPQpn3NFoe"},"payment_method":{"name":"OscarNava","exp_month":"12","exp_year":"22","object":"card_payment","type":"credit","last4":"0002","brand":"visa","issuer":"banamex","account_type":"BANAMEX","country":"MX","fraud_indicators":[]},"object":"charge","description":"Payment from order","status":"declined","amount":10000,"fee":708,"customer_id":"cus_2rFeDEaPCq2GkhLS8","order_id":"ord_2rFeDeCbsDG8R6bK4"},"previous_attributes":{}},"livemode":true,"webhook_status":"failing","webhook_logs":[{"id":"webhl_2rFeDewRPQpn3NFoj","url":"https://www.example.com/conekta/webhooks","failed_attempts":5,"last_http_response_status":598,"object":"webhook_log","last_attempted_at":0}],"id":"61fdc53b0211a6764e57ec53","object":"event","type":"charge.created","created_at":1644021051}'
const isValid = publicKey.verify(payload, signature, 'utf8', 'base64');
if (isValid) {
console.log("Verification succeeded!")
}
Updated almost 2 years ago