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 garantizar que la notificación proviene legítimamente de Forpay y no ha sido alterada.
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.
- Cifrar el string resultante usando HMAC-SHA256 con tu clave HMAC.
- 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);
// 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, Fintoc o EPAC 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
EPAC / 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
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 // Solo si installment.state.id = 2 (PAGADA)
payment.authorizationCode // Solo si installment.state.id = 2 (PAGADA)
payment.amount // Solo si installment.state.id = 2 (PAGADA)
payment.dateTime // Solo si installment.state.id = 2 (PAGADA)
Nota: Los campos de
paymentsolo se incluyen en la concatenación cuando la cuota está en estado PAGADA (installment.state.id = 2). Para otros estados (rechazada, en proceso, etc.), se omiten.
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('');
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 isPaid = p.installment.state.id === 2;
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 (isPaid) {
fields.push(
p.payment.id,
p.payment.authorizationCode,
p.payment.amount,
p.payment.dateTime
);
}
const str = fields.join('');
const computed = crypto
.createHmac('sha256', HMAC_SECRET)
.update(str)
.digest('base64');
return computed === receivedSignature;
}
