- Print
- DarkLight
Páginas de Respuesta y Confirmación
ePayco proporciona dos mecanismos para notificar el resultado de una transacción: la página de respuesta y la URL de confirmación (webhook). Es importante entender la diferencia entre ambas y cómo implementarlas correctamente.
Página de Respuesta (response )
La página de respuesta es la URL a la que se redirige al usuario después de completar el proceso de pago en ePayco Smart Checkout. Esta redirección ocurre en el navegador del cliente y contiene información básica sobre la transacción en los parámetros de la URL.
Características:
- ✅ Redirección visible para el usuario
- ✅ Permite mostrar una página de respuesta personalizada
- ✅ Incluye el parámetro ref_payco en la URL para consultar datos de la transacción
- ⚠️ NO es confiable para validar el estado final de la transacción (el usuario puede cerrar el navegador o perder conexión)
- ⚠️ Los parámetros pueden ser manipulados por el usuario
Comportamiento de redirección:
1. Con URL personalizada: Si se configura una URL de respuesta personalizada, el usuario será redirigido a esa URL con el parámetro ref_payco que permite consultar los datos de la transacción.
2. Sin URL personalizada: Si no se configura una URL de respuesta, ePayco redirige a una página predeterminada que permite al usuario imprimir y reenviar el comprobante de la transacción.
Ejemplo de URL de redirección:
https://mitienda.com/resultado-pago?ref_payco=68fb83729d094878e015be00Para obtener los atributos que se envían en la página de respuesta dinámica deberás hacer un llamado mediante el siguiente endpoint:
curl --location --request GET 'https://secure.epayco.co/validation/v1/reference/68fb83729d094878e015be00' \
--header 'Content-Type: application/json'URL de Confirmación (confirmation ) - Webhook
La URL de confirmación es un webhook que ePayco invoca en tu servidor backend cuando el estado de una transacción cambia. Esta es la forma confiable y segura de validar el resultado final de una transacción.
Características:
- ✅ Confiable: Comunicación servidor a servidor
- ✅ Invocado automáticamente por ePayco
- ✅ Puede reintentar en caso de fallo
- ✅ Incluye firma de seguridad para validar autenticidad
- ⚠️ Debe responder con código HTTP 200 para confirmar recepción
Parámetros recibidos:
Parámetros recibidos (método GET o POST):
| Parámetro | Tipo | Descripción | 
|---|---|---|
| ref_payco | string | Referencia única de la transacción en ePayco | 
| x_cust_id_cliente | string | ID del cliente | 
| x_ref_payco | string | Referencia de pago de ePayco | 
| x_id_invoice | string | ID de la factura o invoice | 
| x_description | string | Descripción del producto o servicio | 
| x_amount | string | Monto de la transacción | 
| x_amount_country | string | Monto en moneda del país | 
| x_amount_ok | string | Monto aprobado | 
| x_tax | string | Valor del impuesto | 
| x_amount_base | string | Base del impuesto | 
| x_currency_code | string | Código de la moneda | 
| x_transaction_id | string | ID de la transacción | 
| x_response | string | Código de respuesta  | 
| x_approval_code | string | Código de aprobación de la transacción | 
| x_response_reason_text | string | Texto descriptivo de la respuesta | 
| x_transaction_date | string | Fecha de la transacción | 
| x_transaction_state | string | Estado de la transacción | 
| x_franchise | string | Franquicia o método de pago utilizado | 
| x_business | string | Nombre del comercio | 
| x_customer_doctype | string | Tipo de documento del cliente | 
| x_customer_document | string | Número de documento del cliente | 
| x_customer_name | string | Nombre del cliente | 
| x_customer_lastname | string | Apellido del cliente | 
| x_customer_email | string | Email del cliente | 
| x_customer_phone | string | Teléfono del cliente | 
| x_customer_country | string | País del cliente | 
| x_customer_city | string | Ciudad del cliente | 
| x_customer_address | string | Dirección del cliente | 
| x_customer_ip | string | IP del cliente | 
| x_signature | string | Firma de seguridad para validar la autenticidad | 
| x_test_request | string | Indica si es transacción de prueba  | 
| 
 | string | Campos personalizados enviados en la petición inicial | 
Ejemplo de implementación:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.post('api/webhook/epayco/confirmation', async (req, res) => {
  try {
    const {
      ref_payco,
      x_ref_payco,
      x_transaction_id,
      x_response,
      x_response_reason_text,
      x_amount,
      x_currency_code,
      x_franchise,
      x_signature,
      x_test_request,
      x_approval_code,
      x_transaction_date,
      x_transaction_state,
      x_customer_email,
      x_customer_name,
      x_extra1,
      x_extra2
    } = req.body;
    // 1. Validar firma de seguridad
    const p_cust_id_cliente = process.env.EPAYCO_CUSTOMER_ID;
    const p_key = process.env.EPAYCO_P_KEY;
    const signature = crypto
      .createHash('sha256')
      .update(`${p_cust_id_cliente}^${p_key}^${x_ref_payco}^${x_transaction_id}^${x_amount}^${x_currency_code}`)
      .digest('hex');
    if (signature !== x_signature) {
      console.error('Firma inválida - posible intento de fraude');
      return res.status(400).send('Invalid signature');
    }
    // 2. Validar que no sea transacción duplicada
    const existingTransaction = await checkTransactionExists(x_transaction_id);
    if (existingTransaction) {
      console.log('Transacción ya procesada:', x_transaction_id);
      return res.status(200).send('OK');
    }
    // 3. Procesar según el estado de la transacción
    switch (x_response) {
      case 'Aceptada':
        await processApprovedTransaction({
          refPayco: ref_payco,
          transactionId: x_transaction_id,
          amount: x_amount,
          currency: x_currency_code,
          customerEmail: x_customer_email,
          customerName: x_customer_name,
          franchise: x_franchise,
          approvalCode: x_approval_code,
          transactionDate: x_transaction_date
        });
        break;
      case 'Rechazada':
        await processRejectedTransaction({
          refPayco: ref_payco,
          transactionId: x_transaction_id,
          reason: x_response_reason_text
        });
        break;
      case 'Pendiente':
        await processPendingTransaction({
          refPayco: ref_payco,
          transactionId: x_transaction_id
        });
        break;
      case 'Fallida':
        await processFailedTransaction({
          refPayco: ref_payco,
          transactionId: x_transaction_id,
          reason: x_response_reason_text
        });
        break;
    }
    // 4. Responder con 200 OK para confirmar recepción
    res.status(200).send('OK');
  } catch (error) {
    console.error('Error procesando webhook:', error);
    res.status(500).send('Internal Server Error');
  }
});
// Funciones auxiliares
async function checkTransactionExists(transactionId) {
  // Implementar consulta a tu base de datos
  return false;
}
async function processApprovedTransaction(data) {
  // Actualizar orden como pagada
  // Enviar email de confirmación
  // Activar servicios/productos
  console.log('Transacción aprobada:', data);
}
async function processRejectedTransaction(data) {
  // Actualizar orden como rechazada
  // Notificar al usuario
  console.log('Transacción rechazada:', data);
}
async function processPendingTransaction(data) {
  // Marcar orden como pendiente
  // Programar verificación posterior
  console.log('Transacción pendiente:', data);
}
async function processFailedTransaction(data) {
  // Manejar transacción fallida
  console.log('Transacción fallida:', data);
}
app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});// Página de respuesta (response.html)
const urlParams = new URLSearchParams(window.location.search);
const transactionData = {
  refPayco: urlParams.get('ref_payco'),
  response: urlParams.get('x_response'),
  transactionId: urlParams.get('x_transaction_id'),
  amount: urlParams.get('x_amount'),
  currency: urlParams.get('x_currency_code'),
  franchise: urlParams.get('x_franchise'),
  approvalCode: urlParams.get('x_approval_code'),
  responseReason: urlParams.get('x_response_reason_text')
};
// Mostrar resultado al usuario
if (transactionData.response === 'Aceptada') {
  showSuccessMessage(transactionData);
} else if (transactionData.response === 'Rechazada') {
  showErrorMessage(transactionData);
} else if (transactionData.response === 'Pendiente') {
  showPendingMessage(transactionData);
}
// ⚠️ IMPORTANTE: NO confíes solo en esta respuesta
// Siempre valida el estado final en tu backend usando el webhookValidación de Firma de Seguridad
La firma de seguridad x_signature permite verificar que la petición proviene realmente de ePayco y no ha sido manipulada.
Fórmula para calcular la firma:
Parámetros necesarios:
- p_cust_id_cliente: Tu Customer ID de ePayco (obtenido del dashboard)
- p_key: Tu P_KEY de ePayco (obtenido del dashboard)
- ^: Carácter separador literal
SHA256(p_cust_id_cliente^p_key^x_ref_payco^x_transaction_id^x_amount^x_currency_code)Ejemplo en diferentes lenguajes:
const crypto = require('crypto');
function validateSignature(data, customerID, pKey) {
  const signature = crypto
    .createHash('sha256')
    .update(`${customerID}^${pKey}^${data.x_ref_payco}^${data.x_transaction_id}^${data.x_amount}^${data.x_currency_code}`)
    .digest('hex');
  return signature === data.x_signature;
}function validateSignature($data, $customerID, $pKey) {
    $signature = hash('sha256', 
        $customerID . '^' . 
        $pKey . '^' . 
        $data['x_ref_payco'] . '^' . 
        $data['x_transaction_id'] . '^' . 
        $data['x_amount'] . '^' . 
        $data['x_currency_code']
    );
    return $signature === $data['x_signature'];
}import hashlib
def validate_signature(data, customer_id, p_key):
    signature_string = f"{customer_id}^{p_key}^{data['x_ref_payco']}^{data['x_transaction_id']}^{data['x_amount']}^{data['x_currency_code']}"
    signature = hashlib.sha256(signature_string.encode()).hexdigest()
    return signature == data['x_signature']Mejores Prácticas
1. Siempre valida en el webhook: Nunca confíes únicamente en la página de respuesta para confirmar pagos.
2. Implementa idempotencia: ePayco puede reintentar el webhook múltiples veces. Verifica que no proceses la misma transacción dos veces.
3. Responde rápidamente: El webhook debe responder con HTTP 200 en menos de 30 segundos. Para procesos largos, usa colas de trabajos.
4. Valida la firma: Siempre verifica la firma de seguridad x_signature para prevenir fraudes.
5. Registra todo: Guarda todos los webhooks recibidos para auditoría y debugging.
6. Maneja reintentos: Si tu servidor está caído, ePayco reintentará enviar el webhook. Asegúrate de manejar esto correctamente.
7. Usa HTTPS: La URL de confirmación debe usar HTTPS para seguridad.
8. No redirijas: El endpoint del webhook no debe hacer redirecciones (301/302).
Ejemplo de Configuración
Al crear la sesión de checkout, configura ambas URLs:
const sessionData = {
  checkout_version: "2",
  name: "Mi Tienda",
  description: "Producto ejemplo",
  currency: "COP",
  amount: 50000,
  lang: "ES",
  // URL donde se redirige al usuario (visible)
  response: "https://mitienda.com/resultado-pago",
  // URL del webhook (servidor a servidor)
  confirmation: "https://mitienda.com/api/webhook/epayco/confirmation"
};Importante: Ambas URLs deben ser accesibles públicamente. Para desarrollo local, puedes usar herramientas como ngrok o localtunnel para exponer tu servidor local.
