Todas las notificaciones enviadas por Forpay incluyen un campo signature en el cuerpo del JSON. Este campo contiene una firma HMAC-SHA256 que debes validar para autenticar los campos incluidos en la receta correspondiente. La cobertura se limita al conjunto y orden de campos descritos en esta página. Valida también la estructura del payload recibido.
Importante: Si la firma calculada por tu sistema no coincide con el valor recibido en
signature, debes rechazar la notificación.
Clave HMAC
Cada empresa recibe una clave HMAC exclusiva para su integración. Esta clave se entrega junto con las credenciales del ambiente correspondiente.
Ejemplo (ambiente de desarrollo):
WGsMT5*****$CAr
Cómo validar la firma
El proceso de validación consiste en:
- Concatenar los campos del mensaje en el orden definido según el tipo de webhook y el método de pago.
- Eliminar todos los caracteres de espacio en blanco del texto concatenado, incluidos los espacios dentro de los valores, mediante
replace(/\s/g, ""). - Calcular HMAC-SHA256 con tu clave HMAC sobre ese texto.
- Codificar el resultado en Base64.
- Comparar el valor obtenido con el campo
signaturedel mensaje recibido. Si son iguales, el mensaje es válido.
Ejemplo de implementación (Node.js)
const crypto = require('crypto');
function validateHmac(payload, secretKey, receivedSignature) {
// 1. Construir el string de concatenación según el tipo de webhook
const concatenated = buildConcatenatedString(payload).replace(/\s/g, "");
// 2. Generar la firma HMAC-SHA256 en Base64
const computedSignature = crypto
.createHmac('sha256', secretKey)
.update(concatenated)
.digest('base64');
// 3. Comparar con la firma recibida
return computedSignature === receivedSignature;
}Webhook de Activación — Campos a concatenar
Los campos a concatenar varían según el método de pago enrolado por el cliente, identificado por paymentMethod.id.
Tarjeta de Crédito, Débito o Prepago (IDs: 3, 4, 11)
Concatenar los siguientes valores en este orden exacto (sin separadores):
businessPartner.businessPartnerId
businessPartner.identificationDocument.documentNumber
businessPartner.identificationDocument.verificationNumber
customer.internalUserId
customer.identificationDocument.documentNumber
customer.identificationDocument.verificationNumber
product.internalId
product.id // Si es null, usar string vacío ""
product.serviceId // Si es null, usar string vacío ""
product.dateTime
product.state.id
paymentMethod.card.last4digits
subscription.internalId
subscription.externalId
Ejemplo de string a cifrar:
277209349719527202964214368831benja12Z3688ZM2024-04-12T16:10:50.977Z16623527[externalId]
Débito Directo, Suscripción PAC o PAC directo (IDs: 5, 13, 14)
Concatenar los siguientes valores en este orden exacto:
businessPartner.businessPartnerId
businessPartner.identificationDocument.documentNumber
businessPartner.identificationDocument.verificationNumber
customer.internalUserId
customer.identificationDocument.documentNumber
customer.identificationDocument.verificationNumber
product.internalId
product.id // Si es null, usar string vacío ""
product.serviceId // Si es null, usar string vacío ""
product.dateTime
product.state.id
paymentMethod.id
paymentMethod.account.number
subscription.internalId
subscription.externalId
ForPAC (ID: 6)
Concatenar los siguientes valores en este orden exacto:
businessPartner.businessPartnerId
businessPartner.identificationDocument.documentNumber
businessPartner.identificationDocument.verificationNumber
customer.internalUserId
customer.identificationDocument.documentNumber
customer.identificationDocument.verificationNumber
product.internalId
product.id // Si es null, usar string vacío ""
product.serviceId // Si es null, usar string vacío ""
product.dateTime
product.state.id
paymentMethod.id
paymentMethod.account.number
paymentMethod.account.document.id
paymentMethod.account.document.state.id
subscription.internalId
subscription.externalId
Anulación de mandato sin medio de pago
Para una notificación de anulación que no incluya paymentMethod ni subscription, concatena los campos base siguientes y aplica la misma eliminación de blancos antes de calcular HMAC-SHA256.
businessPartner.businessPartnerId
businessPartner.identificationDocument.documentNumber
businessPartner.identificationDocument.verificationNumber
customer.internalUserId
customer.identificationDocument.documentNumber
customer.identificationDocument.verificationNumber
product.internalId
product.id // Si es null, usar string vacío ""
product.serviceId // Si es null, usar string vacío ""
product.dateTime
product.state.id
El envío de este evento depende de su habilitación para el comercio.
Webhook de Pagos — Campos a concatenar
Para las notificaciones de pago (tanto exitosas como rechazadas), concatenar los siguientes valores:
businessPartner.businessPartnerId
businessPartner.identificationDocument.documentNumber
businessPartner.identificationDocument.verificationNumber
customer.internalUserId
customer.identificationDocument.documentNumber
customer.identificationDocument.verificationNumber
product.internalId
product.id
product.serviceId
payment.id // Cuando el objeto payment esté presente
payment.authorizationCode // Cuando el objeto payment esté presente
payment.amount // Cuando el objeto payment esté presente
payment.dateTime // Cuando el objeto payment esté presente
Cuando el objeto
paymentesté presente, concatenapayment.id,payment.authorizationCode,payment.amountypayment.dateTimedespués de los campos base. Esto también aplica a una notificación REVERSADA (12) que incluyapayment. Cuando el objeto esté ausente, utiliza solo los campos base. Elimina todos los caracteres de espacio en blanco del texto concatenado antes de calcular el HMAC. Ver Verificación HMAC.
Implementación completa de referencia (Node.js)
const crypto = require('crypto');
const HMAC_SECRET = 'WGsMT5*****$CAr'; // Tu clave HMAC
/**
* Valida la firma HMAC de un webhook de activación con Tarjeta (IDs 3, 4, 11)
*/
function validateCardActivation(payload, receivedSignature) {
const p = payload;
const pm = p.paymentMethod;
const sub = p.subscription;
const str = [
p.businessPartner.businessPartnerId,
p.businessPartner.identificationDocument.documentNumber,
p.businessPartner.identificationDocument.verificationNumber,
p.customer.internalUserId,
p.customer.identificationDocument.documentNumber,
p.customer.identificationDocument.verificationNumber,
p.product.internalId,
p.product.id ?? '',
p.product.serviceId ?? '',
p.product.dateTime,
p.product.state.id,
pm.card.last4digits,
sub.internalId,
sub.externalId,
].join('').replace(/\s/g, '');
const computed = crypto
.createHmac('sha256', HMAC_SECRET)
.update(str)
.digest('base64');
return computed === receivedSignature;
}
/**
* Valida la firma HMAC de un webhook de pago
*/
function validatePaymentWebhook(payload, receivedSignature) {
const p = payload;
const hasPayment = p.payment !== undefined && p.payment !== null;
const fields = [
p.businessPartner.businessPartnerId,
p.businessPartner.identificationDocument.documentNumber,
p.businessPartner.identificationDocument.verificationNumber,
p.customer.internalUserId,
p.customer.identificationDocument.documentNumber,
p.customer.identificationDocument.verificationNumber,
p.product.internalId,
p.product.id,
p.product.serviceId,
];
if (hasPayment) {
fields.push(
p.payment.id,
p.payment.authorizationCode,
p.payment.amount,
p.payment.dateTime
);
}
const str = fields.join('').replace(/\s/g, '');
const computed = crypto
.createHmac('sha256', HMAC_SECRET)
.update(str)
.digest('base64');
return computed === receivedSignature;
}
