ServiciosAdministración de Webhooks

Integración con webhooks de pagos

Esta integración te permite recibir notificaciones de transacciones (pagos, devoluciones o anulaciones) en un formato específico mediante una solicitud POST.

Requisitos

  • Servidor que soporte peticiones POST para recibir mensajes

Resultado

  • Suscripción configurada y validación de mensajes implementada

Suscripción a eventos

Si eres cliente

  1. Configura una URL pública y segura para recibir webhooks.
  2. Crea una suscripción para comenzar a recibir las notificaciones.
  3. Valida los webhooks entrantes usando el token correspondiente para asegurar su origen y evitar manipulaciones.

Si eres comercio

  1. Configura una URL pública y segura para recibir webhooks.
  2. Crea una suscripción para comenzar a recibir las notificaciones.
  3. Valida los webhooks entrantes usando el token correspondiente para asegurar su origen y evitar manipulaciones.
ℹ️

Se considera exitosa la entrega cuando tu servidor responde con cualquier código 2xx dentro de los 10 segundos. Si responde con un código distinto de 2xx (4xx o 5xx) o no responde en ese tiempo, haremos nuevos reintentos por algunas horas (hasta 10 intentos, con una espera inicial de 30 segundos que se duplica en cada intento, con un máximo de 90 minutos entre intentos). Luego de este período, se dejará de intentar enviar. Por lo cual, recuerda que es una buena práctica conciliar con cierta periodicidad y de esta manera te asegurarás de contar con toda la información actualizada.

Para conciliar recomendamos utilizar el servicio de obtención de transacciones.

Ejemplo de notificación:

OPERATION_CREATED

{
  "notification_type": "OPERATION_CREATED",
  "merchant_id": "db3ae307-4a5c-4cbb-b48c-80bf1a3fde1d",
  "user": "[email protected]",
  "datetime": "2022-12-31T23:23:23Z",
  "detail": {
    "terminal_id": "72171704-7806-4347-b08b-bc2d2d96e68e",
    "operation_id": "2fce7d49-f3e2-4b1f-a7bb-7f16d3ea64a2",
    "ticket_id": "111111111",
    "operation_type": "PAYMENT",
    "qr_id": null,
    "operation_status": "APPROVED",
    "currency": "ARS",
    "operation_amount": "200000",
    "operation_additional_info": "ABC1234",
    "payment_method_type": "DEBIT",
    "payment_method_detail": "VISA"
  }
}

TAXED_OPERATION_CREATED

{
  "notification_type": "TAXED_OPERATION_CREATED",
  "merchant_id": "c2192cf8-e221-4ccc-83d4-1c2105d46b9a",
  "user": "[email protected]",
  "datetime": "2023-09-19T20:07:32Z",
  "detail": {
    "customer_id": "a9733025-9b74-4cf9-b4fb-f71eeb037054",
    "merchant_id": "45b1d469-e3e5-4ac6-8fe2-2e620d00f9a8",
    "terminal_id": "c08d4aa6-d560-41c1-acbf-b183eb3794dd",
    "merchant_additional_info": "35701",
    "transaction_id": "1b35e569-dc28-47be-897f-1c2a9e5a5305",
    "operation_id": "6debca65-4faf-48fd-a065-faf32735a52a",
    "operation_number": 183863925,
    "operation_additional_info": "",
    "serial_number": "98282329166214",
    "operation_type": "PAYMENT",
    "payment_method": "CREDIT",
    "gross_amount": 21,
    "currency": "ARS",
    "datetime": "2024-03-05T20:21:03-03:00",
    "status": "APPROVED",
    "installments": 1,
    "financing": "ESTANDAR",
    "user": "[email protected]",
    "acquirer": "PRISMA",
    "operation_detail": {
        "card": {
            "card_bin": "47617390",
            "card_mask": "XXXXXXXXXXXX0119",
            "card_brand": "VISA",
            "is_international_card": false
        },
        "holder_name": "Tarjetahabiente",
        "holder_document": "",
        "description": "APROBADO",
        "input_mode": "CONTACTLESS",
        "reference_operation_number": 394221095,
        "reference_operation_id": "50c7571f-750c-43b5-9baf-f5d1d9a4d022",
        "rrn": "1234567890",
        "authorization_code": "123456"
    },
    "tax_info": {
      "term": 2,
      "payment_date": "2024-02-23T09:26:54-03:00",
      "net_amount": 18.02,
      "customer_term": 8,
      "customer_payment_date": "2024-03-04T09:26:54-03:00",
      "customer_net_amount": 20.54,
      "tax_breakdown": [
        {
          "tax_code": "MENTA_TO_CUSTOMER_COMMISSION",
          "reference": "Menta to customer commission",
          "amount": 0.07,
          "rate": 0.35
        },
        {
          "tax_code": "MENTA_TO_CUSTOMER_COMMISSION_VAT_TAX",
          "reference": "Menta to customer commission VAT Tax",
          "amount": 0.02,
          "rate": 0.21
        },
        {
          "tax_code": "ACQUIRER_TO_CUSTOMER_COMMISSION",
          "reference": "Acquirer to customer commission",
          "amount": 0.38,
          "rate": 1.8
        },
        {
          "tax_code": "ACQUIRER_TO_CUSTOMER_COMMISSION_VAT_TAX",
          "reference": "Acquirer to customer commission VAT Tax",
          "amount": 0.08,
          "rate": 0.21
        },
        {
          "tax_code": "CUSTOMER_TO_MERCHANT_COMMISSION",
          "reference": "Customer to merchant commission",
          "amount": 1.26,
          "rate": 5.99
        },
        {
          "tax_code": "CUSTOMER_TO_MERCHANT_COMMISSION_VAT_TAX",
          "reference": "Customer to merchant commission VAT Tax",
          "amount": 0.26,
          "rate": 0.21
        },
        {
          "tax_code": "MERCHANT_VAT_TAX",
          "reference": "Merchant VAT Tax",
          "amount": 0.58,
          "rate": 0.21
        },
        {
          "tax_code": "MERCHANT_INCOME_TAX",
          "reference": "Merchant Income Tax",
          "amount": 0.19
        },
        {
          "tax_code": "MERCHANT_IIBB_TAX",
          "reference": "Merchant IIBB Tax",
          "amount": 0.68
        }
      ]
    }
}
}
ℹ️

En TAXED_OPERATION_CREATED, si la operación no tiene cálculo de impuestos, operation_detail.description contiene el texto fijo Error al calcular los impuestos en lugar de la descripción de la operación.

Validación de los webhooks

Para garantizar que su servidor solo procese entregas de webhooks enviadas por Menta y para asegurarse de que la entrega no haya sido manipulada, es fundamental validar la firma del webhook antes de continuar con el procesamiento.

Menta utiliza la secret key proporcionada durante la suscripción para generar un hash de firma distinto en cada solicitud, la cual se envía como valor del encabezado X-Menta-Signature-V1 en cada webhook. Para verificar que la firma es auténtica, el cliente debe aplicar la misma función de hash (HMAC con SHA-256) junto con la secret key, y asegurarse de que coincida con la firma enviada.

Pasos para generar y validar firma

  1. Concatenar los siguientes elementos:
TimestampEl punto ”.”El cuerpo (payload) de la solicitud.
enviado en el encabezado X-Menta-Signature-Timestamp.cuerpo crudo, en JSON compacto (sin espacios ni saltos de línea)

La firma se calcula sobre el cuerpo crudo tal como se envía: JSON compacto, sin espacios ni saltos de línea, con los campos nulos incluidos (por ejemplo "qr_id":null). Usa los bytes recibidos sin modificar su formato.

  1. Obtener la secret key guardada en su servidor.
  2. Aplicar la función de hash HMAC con algoritmo SHA-256 junto con la secret key.
  3. Comparar la firma generada con la firma enviada en el encabezado X-Menta-Signature-V1.

Ejemplo de implementación en Kotlin

Puedes utilizar el lenguaje de programación que prefieras para implementar la verificación HMAC en su código.

import java.util.Formatter
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
 
class HashProvider {
 
  fun generateHMAC(timestamp: String, body: String, secretKey: String): String {
    val msg = "$timestamp.$body"
    val signingKey = SecretKeySpec(secretKey.toByteArray(), "HmacSHA256")
    val mac = Mac.getInstance("HmacSHA256")
    mac.init(signingKey)
    val bytes = mac.doFinal(msg.toByteArray())
    return format(bytes)
  }
 
  private fun format(bytes: ByteArray): String {
    val formatter = Formatter()
    bytes.forEach { formatter.format("%02x", it) }
    return formatter.toString()
  }
}

Probando validación

Para probar la validación de webhooks, puedes utilizar los siguientes valores de secret key, timestamp (expresado como Unix timestamp) y JSON payload:

secret keytimestamp
secretKey!1697657734

Payload (se muestra formateado para facilitar la lectura):

{
  "notification_type": "OPERATION_CREATED",
  "merchant_id": "c2192cf8-e221-4ccc-83d4-1c2105d46b9a",
  "user": "[email protected]",
  "datetime": "2023-07-23T21:10Z",
  "detail": {
    "terminal_id": "39b4500d-8e14-45ae-89e2-63135534132b",
    "operation_id": "8e02915b-9387-412c-946a-bf9c046f62ff",
    "ticket_id": "50691299",
    "operation_type": "PAYMENT",
    "qr_id": null,
    "operation_status": "APPROVED",
    "currency": "ARS",
    "operation_amount": "100",
    "operation_additional_info": "",
    "payment_method_type": "DEBIT",
    "payment_method_detail": "MASTERCARD"
  }
}

El cuerpo sobre el que se calcula la firma es el JSON compacto, tal como se envía:

{"notification_type":"OPERATION_CREATED","merchant_id":"c2192cf8-e221-4ccc-83d4-1c2105d46b9a","user":"[email protected]","datetime":"2023-07-23T21:10Z","detail":{"terminal_id":"39b4500d-8e14-45ae-89e2-63135534132b","operation_id":"8e02915b-9387-412c-946a-bf9c046f62ff","ticket_id":"50691299","operation_type":"PAYMENT","qr_id":null,"operation_status":"APPROVED","currency":"ARS","operation_amount":"100","operation_additional_info":"","payment_method_type":"DEBIT","payment_method_detail":"MASTERCARD"}}

Si tu implementación es correcta, las firmas que generes deben coincidir con los siguientes valores de firma:

el resultado de la firma y el header X-Menta-Signature-V1: 58f8e39497b01f53d13c5144fcd74ddc3bb33aee35d99cd4989b5e04bdf216f7

Recomendaciones

  1. Valida el timestamp incluido en la solicitud para evitar ataques de repetición (replay-attack). Un ataque de repetición ocurre cuando un atacante intercepta una solicitud válida junto con su firma y la retransmite. El timestamp se incluye en el proceso de hash y es verificado por la firma, por lo que un atacante no puede cambiar el timestamp sin invalidar la firma. Si la firma es válida pero el timestamp es demasiado antiguo, se recomienda rechazar la solicitud.

  2. Asegúrate de que el payload y los encabezados no se modifiquen antes de la verificación. Si utilizas un proxy o un equilibrador de carga, asegúrate de que estos componentes no modifiquen el payload ni los encabezados.

  3. Si tu lenguaje de programación y la implementación del servidor especifican una codificación de caracteres, asegúrate de manejar el payload como UTF-8. Y además, se encuentren en Formato JSON antes de generar el hash.

  4. Ten en consideración la lógica de reintentos mencionada y ten un control posterior de conciliación para evitar perder información relevante.