Aprobación dinámica
SPEI Dynamic Approval
Feature flag:
spei_dynamic_approval_enabled
¿Qué es?
Por defecto, cuando un cliente realiza una transferencia SPEI a la CLABE generada por Conekta, la orden se marca automáticamente como pagada sin intervención del merchant.
Con SPEI Dynamic Approval, Conekta envía un webhook al endpoint del merchant por cada intento de pago y espera una respuesta síncrona: el merchant responde payable: true para aprobar o payable: false para rechazar. La decisión ocurre en tiempo real, en la misma respuesta HTTP — no hay una llamada API separada.
Aplica tanto para cargos SPEI de un solo uso (spei) como para CLABE recurrente (spei_recurrent).
Sin Dynamic Approval:
Transferencia recibida → order.paid (automático)
Con Dynamic Approval:
Transferencia recibida → POST a endpoint del merchant (inbound_payment.payment_attempt)
← { "payable": true } → order.paid
← { "payable": false } → order sigue en pending_payment (cliente puede reintentar)
Requisitos previos
- Tener una cuenta Conekta activa con acceso a la API.
- Solicitar la habilitación del flag
spei_dynamic_approval_enableda tu ejecutivo de cuenta o a soporte Conekta. - Exponer un endpoint HTTPS en tu servidor capaz de responder en menos de 2 segundos.
- Configurar ese endpoint como webhook en el Panel Conekta con el evento
inbound_payment.payment_attempthabilitado.
Diferencia entre SPEI de un solo uso y CLABE recurrente
El comportamiento del evento inbound_payment.payment_attempt varía según el tipo de integración:
SPEI (spei) | CLABE recurrente (spei_recurrent) | |
|---|---|---|
| ¿Existe una orden previa? | Sí — creada por el merchant antes del pago | No — no hay orden al momento del intento |
charge_id en el payload | Apunta al charge de la orden existente | No hay orden ni charge previo |
| ¿Cómo identificar al cliente? | Por customer_id o charge_id | Solo por customer_id y los datos del remitente en payment_attempts |
| ¿Qué sucede al aprobar? | La orden existente pasa a paid | Conekta crea la orden y el cargo en ese momento |
Para CLABE recurrente, la información del remitente dentro de
payment_attempts(nombre, RFC, CLABE origen, monto) es la única fuente de datos disponible para tomar la decisión de aprobación.
Flujo completo
SPEI de un solo uso
Merchant (servidor) Conekta Cliente
| | |
|-- POST /orders -------->| |
| payment_method: spei | |
|<-- CLABE + charge_id ---| |
| | |
|-- Muestra CLABE al cliente |
| |<--- Transferencia SPEI ------|
| | |
|<-- inbound_payment.payment_attempt (con charge_id) ---|
| | |
|-- HTTP 200 ------------>| |
| { "payable": true } | |
| | |
|<-- Webhook: order.paid -| |
CLABE recurrente
Merchant (servidor) Conekta Cliente
| | |
|-- POST /customers ----->| |
| spei_recurrent | |
|<-- CLABE permanente ----| |
| | |
|-- Comparte CLABE al cliente (una sola vez) |
| |<--- Transferencia SPEI ------|
| | |
|<-- inbound_payment.payment_attempt (sin orden previa) |
| | |
|-- HTTP 200 ------------>| |
| { "payable": true } | |
| | |
|<-- Webhook: order.paid -| (Conekta crea orden/cargo) |
Implementación paso a paso
1a. Crear una orden con SPEI (cargo único)
curl --location --request POST 'https://api.conekta.io/orders' \
--header 'Accept: application/vnd.conekta-v2.2.0+json' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer key_xxxxxxxxxxxxxxxx' \
--data-raw '{
"currency": "MXN",
"customer_info": {
"name": "Juan Pérez",
"email": "[email protected]",
"phone": "+525512345678"
},
"line_items": [
{
"name": "Producto ejemplo",
"unit_price": 50000,
"quantity": 1
}
],
"charges": [
{
"payment_method": {
"type": "spei"
}
}
]
}'La respuesta incluye una clabe única para esa orden. Muéstrasela al cliente para que realice su transferencia.
1b. Crear CLABE recurrente (spei_recurrent)
spei_recurrent)curl --location --request POST 'https://api.conekta.io/customers' \
--header 'Accept: application/vnd.conekta-v2.2.0+json' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer key_xxxxxxxxxxxxxxxx' \
--data-raw '{
"name": "Juan Pérez",
"email": "[email protected]",
"phone": "+525512345678",
"payment_sources": [
{
"type": "spei_recurrent"
}
]
}'La respuesta incluye una clabe permanente asociada al cliente. Cada transferencia que el cliente haga a esa CLABE generará un evento inbound_payment.payment_attempt.
2. Configurar el Webhook
- Accede a panel.conekta.com → Desarrolladores → Webhooks.
- Haz clic en Crear Webhook.
- Ingresa la URL de tu endpoint (debe ser HTTPS y públicamente accesible).
- Habilita los eventos:
inbound_payment.payment_attempt← obligatorio para el flujo de aprobaciónorder.paid← para confirmar cuándo la orden quedó pagada
- Haz clic en Confirmar — el webhook debe aparecer como activo.
Si usas firewall, permite el IP de Conekta:
52.200.151.182(puertos 80, 443 o 1025–10001).
3. Implementar el endpoint de aprobación
Cuando Conekta recibe una transferencia SPEI, hace un POST síncrono a tu endpoint. Tu servidor debe:
- Evaluar si el pago es válido según tus reglas de negocio.
- Responder con HTTP 200 en menos de 2 segundos.
- Incluir
{ "payable": true }para aprobar o{ "payable": false }para rechazar.
Si no respondes en 2 segundos, Conekta rechaza el intento automáticamente.
Payload entrante:
{
"data": {
"object": {
"payment_method": {
"clabe": "734180008033354999",
"bank": "FINCO PAY",
"issuing_account_holder_name": null,
"issuing_account_tax_id": null,
"issuing_account_bank": null,
"issuing_account_number": null,
"receiving_account_holder_name": null,
"receiving_account_tax_id": null,
"receiving_account_number": "734180008033354999",
"receiving_account_bank": "FINCO PAY",
"reference_number": null,
"description": null,
"tracking_code": null,
"executed_at": null,
"payment_attempts": [
{
"status": null,
"failure_code": null,
"failure_message": null,
"issuing_account_number": "014890568660710403",
"issuing_account_bank": "40014",
"tracking_code": "2026041540014TRAPP033414020200",
"issuing_account_holder_name": "JARED CAMACHO",
"issuing_account_tax_id": "EACG030618Q9X",
"reference_number": "8484259",
"description": "TRANSFERENCIA A JARED CAMACHO",
"amount": 3000,
"executed_at": 1776259138
}
],
"object": "bank_transfer_payment",
"type": "spei",
"expires_at": 0
},
"charge_id": "69dfe4a2a3ad1f00164177gf",
"livemode": true,
"created_at": 1776280738,
"object": "inbound_payment",
"amount": 3000,
"currency": "MXN",
"customer_id": "cus_2zrEd24vJqaX6ZVXX",
"customer_custom_reference": null
},
"previous_attributes": {}
}
}Nota para CLABE recurrente: el
charge_idno apunta a una orden previa — no existe orden al momento del intento. La decisión debe tomarse usandocustomer_idy los datos del remitente enpayment_attempts.
Respuesta para aprobar:
HTTP 200
{ "payable": true }Respuesta para rechazar:
HTTP 200
{ "payable": false }Ejemplo en Node.js (Express):
app.post('/webhook/spei-approval', async (req, res) => {
const { type, data } = req.body;
if (type === 'inbound_payment.payment_attempt') {
const inboundPayment = data.object;
const attempt = inboundPayment.payment_method.payment_attempts[0];
// Aplica tus reglas de negocio.
// Para cargo único: puedes cruzar charge_id con tus órdenes.
// Para recurrente: no hay orden previa; valida por customer_id y datos del remitente.
const isValid = await validatePayment({
chargeId: inboundPayment.charge_id, // null si es recurrente sin orden previa
customerId: inboundPayment.customer_id,
amount: inboundPayment.amount,
clabe: inboundPayment.payment_method.clabe,
senderName: attempt.issuing_account_holder_name,
senderTaxId: attempt.issuing_account_tax_id,
trackingCode: attempt.tracking_code,
});
// Responder SIEMPRE con HTTP 200; el campo payable decide la acción
return res.status(200).json({ payable: isValid });
}
// order.paid y otros eventos asincrónicos no requieren respuesta especial
return res.status(200).send('ok');
});4. Recibir confirmación del pago
Si aprobaste el intento, Conekta dispara el evento asíncrono order.paid con la orden completada. En el caso de CLABE recurrente, Conekta crea la orden y el cargo en este momento.
Este evento es informativo — responde HTTP 200 y procede a cumplir el pedido.
Eventos relevantes
| Evento | Tipo | Cuándo se dispara |
|---|---|---|
inbound_payment.payment_attempt | Síncrono | Conekta recibió una transferencia SPEI y espera aprobación (max 2s) |
order.paid | Asíncrono | El merchant aprobó (payable: true) y la orden quedó pagada |
Al rechazar (
payable: false) no se genera ningún evento. La orden enpending_paymentsigue activa (cargo único) o el cliente puede volver a transferir (recurrente).
Estados de la orden
Cargo único (spei)
spei)created → pending_payment ── payable: true ──→ paid
↑
└── payable: false (sigue en pending_payment, acepta nuevos intentos)
CLABE recurrente (spei_recurrent)
spei_recurrent)(sin orden previa) ── payable: true ──→ order creada → paid
── payable: false ──→ (no se crea nada)
Preguntas frecuentes
¿Tengo que llamar a un endpoint de Conekta para aprobar?
No. La aprobación ocurre directamente en la respuesta HTTP al webhook. Respondes { "payable": true } y Conekta completa la orden.
¿Qué pasa si mi servidor tarda más de 2 segundos? Conekta rechaza el intento automáticamente.
¿El cliente puede volver a transferir si fue rechazado?
Sí. Para cargo único, la orden sigue en pending_payment. Para recurrente, el cliente puede transferir nuevamente en cualquier momento; cada intento genera un nuevo evento.
¿Puedo activarlo solo para algunas órdenes?
No. El flag spei_dynamic_approval_enabled aplica a nivel de company. Si está habilitado, todos los pagos SPEI del merchant pasan por el flujo de aprobación.
Soporte
- Documentación completa: developers.conekta.com
- Solicitar habilitación del flag: contacta a tu ejecutivo de cuenta Conekta
- Soporte técnico: [email protected]
Updated about 6 hours ago
