🚀 Lanzamiento v2.3: Activa y desactiva métodos de pago para tu Conekta Checkout desde el Panel, sin tocar tu código. Ver documentación →

Migrar de Dispersión a Terceros a Dispersiones a Terceros vía SPEI

Guía de migración para negocios que ya usan /payees + /transfers y quieren pasar al nuevo flujo de payout_rules

Si tu integración dispersa dinero a terceros con POST /payees y POST /transfers, esta guía te muestra qué reemplaza a qué en Dispersiones a Terceros vía SPEI, y los cambios de comportamiento que rompen una migración hecha 1:1.

El flujo nuevo separa dos cosas que antes iban juntas: el beneficiario, que ahora se valida contra el banco antes de poder recibir dinero, y la instrucción de dispersión, que ahora es una regla que Conekta ejecuta por ti — una vez, en una fecha, o con una frecuencia — en lugar de una orden que disparas cada vez desde tu código.

📘

¿Sigue funcionando mi integración vieja?

Sí — ambos flujos operan en paralelo hoy. Puedes migrar por partes: mover un beneficiario al flujo nuevo no afecta a los que sigues dispersando con /transfers. Confirma con tu Ejecutivo de Cuentas o [email protected] si tu negocio tiene una fecha de corte planeada para /payees y /transfers.

Qué ganas al migrar

  • Validación de la cuenta destino antes de mandar dinero. Conekta confirma con el banco que la CLABE existe y corresponde al titular declarado. Una CLABE mal capturada se detecta en el alta, no cuando el dinero ya salió.
  • Programación y recurrencia sin código propio. Los pagos a fecha fija o con periodicidad se configuran como reglas (rule_type: scheduled o recurrent). Si hoy mantienes un cron que llama a /transfers, puedes retirarlo.
  • Alta masiva de beneficiarios por CSV.
  • Consulta de estado por APIGET /payouts/disbursements y GET /payouts/batches, sin depender de haber guardado el payload del webhook.
  • Cancelación antes de la ejecución — una instrucción one_time que todavía no se ejecutó se puede eliminar; en /transfers no había forma de dar marcha atrás.
  • Fondeo propio por SPEI, además del saldo generado por tus cobros en Conekta.

Lo que no cambia es el tiempo: igual que con /transfers, una instrucción se crea para un día posterior y se ejecuta ese día en el proceso nocturno.

Qué reemplaza a qué

AcciónAntes (v2.2.0)Ahora (SPEI)Nota
Crear beneficiarioPOST /payees — objeto payee con destinations[]POST /payouts/payees (doc) — objeto external_payee, una sola clabeUn beneficiario por CLABE — ver Un payee ahora es una sola CLABE
Editar beneficiarioEditabas destinations[]PATCH /payouts/payees/{id} — solo alias, rfc, email, o pausar/reactivarNo puedes editar name ni clabe — ver Editar un beneficiario tiene límites
Alta masivaNo existíaPOST /payouts/payees/bulk (CSV)Funcionalidad nueva
Enviar dinero puntualPOST /transfers (ejecución inmediata al siguiente día hábil)POST /payout_rules con rule_type: one_time (doc)Crea una instrucción, no un pago — ver La instrucción no es una orden de ejecución
Pago programado o recurrenteLo resolvías con un cron propioPOST /payout_rules con rule_type: scheduled o recurrentPuedes retirar tu scheduler
Cancelar una instrucciónNunca — no se podía cancelar una vez enviadaDELETE /payout_rules/{id}Sin restricción para scheduled/recurrent; one_time solo mientras no se haya ejecutado
Reintentar un pago fallido por saldoNo existíaPOST /v1/payouts/instructions/{id}/retry (manual), o auto_retry_on_insufficient_funds en la instrucción (automático, opt-in)Ver Seguimiento de dispersiones
Consultar el estado de un pagoVía el payload del webhookGET /payouts/disbursements, GET /payouts/payees/{id}/disbursements (doc)
Consultar el proceso del díaNo existíaGET /payouts/batches
Webhooks de dispersiónpayout.created, payout.paid_out, payout.failedpayout.created, payout.in_transit, payout.paid_out, payout.failed, payout.retryingin_transit y retrying son nuevos
Webhooks de beneficiarioNo existíanpayee.payout_method.created / .updated / .deleted
FondeoSolo desde saldo generado por cobros en ConektaSaldo disponible o depósito directo por SPEI

Cómo funciona el flujo nuevo

sequenceDiagram
    autonumber
    participant Tu as Tu servidor
    participant Conekta
    participant Banco as Banco del beneficiario
    Tu->>Conekta: POST /payouts/payees (alias + name + clabe)
    Conekta-->>Tu: external_payee · pending_validation
    Conekta->>Banco: Validación de la cuenta destino
    Banco-->>Conekta: Resultado
    Conekta-->>Tu: Webhook payee.payout_method.updated · validated_active
    Tu->>Conekta: POST /payout_rules (payee_id, amount en pesos, rule_type)
    Conekta-->>Tu: Instrucción creada · scheduled
    Note over Conekta: Llega el día de la instrucción y hay fondos en la cuenta de dispersiones
    Conekta->>Banco: Transferencia SPEI
    Conekta-->>Tu: Webhook payout.in_transit → payout.paid_out
  1. Das de alta al beneficiario con POST /payouts/payees. La respuesta trae validation_status: pending_validation: el beneficiario existe pero todavía no puede recibir dinero.
  2. Conekta valida la cuenta contra el banco (normalmente menos de dos minutos). El resultado te llega por el webhook payee.payout_method.updated, o lo consultas con GET /payouts/payees/{id}.
  3. Creas la instrucción con POST /payout_rules, apuntando al payee_id ya validado.
  4. Conekta ejecuta la instrucción el día que corresponde, cuando hay fondos disponibles en tu cuenta de dispersiones.
  5. Sigues el resultado por los webhooks payout.* o consultando GET /payouts/disbursements.

Estados del beneficiario

Solo un beneficiario en validated_active puede recibir dispersiones:

stateDiagram-v2
    [*] --> pending_validation: POST /payouts/payees
    pending_validation --> validated_active: cuenta confirmada
    pending_validation --> rejected: la validación falla
    validated_active --> validated_inactive: PATCH validation_status
    validated_inactive --> validated_active: PATCH validation_status
    rejected --> [*]: eliminar y recrear

Importante

PATCH nunca dispara una nueva validación: solo edita alias/rfc/email o alterna validation_status entre validated_active y validated_inactive. No puedes editar name ni clabe. Si capturaste mal el nombre del titular o la CLABE, la corrección es eliminar el beneficiario — su alias queda libre — y crear uno nuevo. Un beneficiario rejected tampoco se puede reactivar; solo eliminarse y recrearse.

Cambios que no son 1:1

Estos cinco puntos son los que causan incidentes en una migración hecha por reemplazo directo de endpoints.

Un payee ahora es una sola CLABE

Un payee podía tener varios destinations[] bajo un mismo payee_id, con un default_destination_id. El external_payee tiene un solo campo clabe.

Si tenías un beneficiario con dos o más destinos, en el flujo nuevo son dos o más beneficiarios distintos, cada uno con su propio id. Usa el campo alias (obligatorio) para distinguirlos — es el rol que antes cumplía default_destination_id.

Los payee_id del flujo viejo no sirven en el flujo nuevo. Si tu base de datos guarda payee_id de /payees, necesitas una tabla de mapeo: cada beneficiario recreado devuelve un id nuevo.

Editar un beneficiario tiene límites

PATCH /payouts/payees/{id} solo acepta alias, rfc, email, y el cambio de validation_status entre validated_active y validated_inactive. No existe una forma de editar name ni clabe — si tu integración vieja corregía datos de un destination en el mismo payee, ese patrón no existe aquí: tienes que eliminar el beneficiario mal capturado y crear uno nuevo.

La validación del beneficiario puede fallar

POST /payees devolvía un beneficiario listo para usar. POST /payouts/payees inicia una validación bancaria asíncrona, y rejected es un desenlace normal, no un error de tu integración — un beneficiario rechazado no puede recibir dispersiones y no se arregla reintentando la misma carga: hay que corregirlo (eliminar y recrear) o contactar soporte si la CLABE es correcta. Contempla ese estado en tu manejador de payee.payout_method.updated desde el primer día.

Importante

Si tu integración vieja creaba el payee y creaba la dispersión inmediatamente después, ese patrón ya no funciona. POST /payout_rules responde 422 si el payee_id no está en validated_active. Espera el webhook payee.payout_method.updated, o haz polling a GET /payouts/payees/{id}, antes de crear la primera instrucción de un beneficiario nuevo.

La instrucción no es una orden de ejecución

POST /transfers era una orden: se creaba y se pagaba. POST /payout_rules crea una instrucción que Conekta evalúa y ejecuta el día que corresponde según el rule_typeone_time en el próximo proceso nocturno, scheduled en la fecha que definas, recurrent según la frecuencia. Si tu código asumía que crear la transferencia equivalía a haberla pagado, esa suposición hay que reemplazarla por seguir los webhooks payout.* o consultar GET /payouts/disbursements.

El monto cambia de unidad

POST /transfers recibía amount en centavos como entero. POST /payout_rules recibe amount en pesos como string.

AntesAhora
$50.00 MXN"amount": 5000"amount": "50.00"
$1,250.75 MXN"amount": 125075"amount": "1250.75"

Importante

Este es el cambio que rompe una migración sin dar error. Si reenvías el valor en centavos como string, "5000" es un string válido que la API acepta — y acabas de crear una instrucción de $5,000.00 en lugar de $50.00. Verifica el monto de tus primeras dispersiones en modo de pruebas antes de mover tráfico real.

Los montos que consultas (GET /payouts/disbursements, GET /payouts/batches) siguen en centavos. La unidad en pesos aplica solo al crear o editar la instrucción.

Próximamente

Vas a poder configurar un límite máximo de monto por beneficiario: toda instrucción que lo exceda se rechazará antes de ejecutarse, y un beneficiario sin límite configurado eventualmente no podrá recibir pagos. Todavía no está disponible en v2.3.0 — no lo asumas en tu integración; te avisaremos cuando salga.

Cómo migrar tu integración

  1. Actualiza la versión de API. Cambia el header Accept — o su equivalente en tu SDK — a application/vnd.conekta-v2.3.0+json.
  2. Recrea tus beneficiarios con POST /payouts/payees, uno por CLABE. Si son muchos, usa el alta masiva por CSV.
  3. Guarda el mapeo payee_id viejo → id nuevo en tu base de datos.
  4. Espera validated_active antes de crear cualquier instrucción para ese beneficiario, y maneja el desenlace rejected.
  5. Reemplaza cada POST /transfers por POST /payout_rules: las llamadas ad hoc pasan a rule_type: one_time; lo que tu cron disparaba en fechas fijas o con periodicidad pasa a scheduled o recurrent, y retiras el cron.
  6. Convierte amount de centavos a pesos-string en el punto donde armas el body de la instrucción.
  7. Actualiza tu manejador de webhooks: agrega payout.in_transit, payout.retrying, y los nuevos payee.payout_method.*. La verificación de firma es obligatoria.
  8. Decide tu estrategia de reintento por saldo insuficiente: activa auto_retry_on_insufficient_funds en las instrucciones que quieras que se recreen solas (hasta 5 días hábiles), o maneja el reintento manual desde tu código con POST /v1/payouts/instructions/{id}/retry.
  9. Reemplaza tu lógica de conciliación por GET /payouts/disbursements o GET /payouts/batches en lugar de depender del payload del webhook que guardaste.
  10. Prueba el flujo completo en modo de pruebas — montos, beneficiarios validados y webhooks recibidos — antes de cambiar a llaves productivas.
  11. Coordina la fecha de corte del flujo viejo con tu Ejecutivo de Cuentas o [email protected].

Antes y después, en código

Crear el beneficiario:

# Antes — v2.2.0
curl -X POST https://api.conekta.io/payees \
  -H "Accept: application/vnd.conekta-v2.2.0+json" \
  -H "Authorization: Bearer key_xxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Juan Pérez García",
    "destinations": [
      { "type": "clabe", "account_number": "646180157034084695" }
    ]
  }'
# Ahora — v2.3.0
curl -X POST https://api.conekta.io/payouts/payees \
  -H "Accept: application/vnd.conekta-v2.3.0+json" \
  -H "Authorization: Bearer key_xxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "alias": "proveedor-norte",
    "name": "Juan Pérez García",
    "clabe": "646180157034084695"
  }'

La respuesta trae el beneficiario en validación:

{
  "id": "payee_xxxxxx",
  "object": "external_payee",
  "name": "Juan Pérez García",
  "alias": "proveedor-norte",
  "clabe": "**************4695",
  "bank_code": "646",
  "validation_status": "pending_validation"
}

Enviar $50.00 MXN a ese beneficiario:

# Antes — v2.2.0 · amount en centavos, ejecución implícita
curl -X POST https://api.conekta.io/transfers \
  -H "Accept: application/vnd.conekta-v2.2.0+json" \
  -H "Authorization: Bearer key_xxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "payee_id": "payee_xxxxxx", "amount": 5000 }'
# Ahora — v2.3.0 · amount en pesos como string, crea una instrucción
curl -X POST https://api.conekta.io/payout_rules \
  -H "Accept: application/vnd.conekta-v2.3.0+json" \
  -H "Authorization: Bearer key_xxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "rule_type": "one_time",
    "payee_id": "payee_xxxxxx",
    "amount": "50.00",
    "name": "Liquidación proveedor"
  }'

Errores: qué cambia

El catálogo genérico de /transfers (authentication_error, processing_error por saldo insuficiente, parameter_validation_error por CLABE o payee inválidos) se reemplaza por respuestas 422 específicas por endpoint, documentadas en cada página:

SituaciónAntesAhora
Beneficiario aún no validadoNo existía el estado422 al crear la instrucción; espera validated_active
CLABE que el banco no reconoceFallaba al transferirEl beneficiario queda en rejected en el alta, antes de mover dinero
Saldo insuficiente el día de la ejecuciónprocessing_error · Insufficient funds, sin reintentoLa dispersión queda failed. Se recrea sola al siguiente día hábil solo si la instrucción tiene auto_retry_on_insufficient_funds activo (opt-in, hasta 5 intentos, evento payout.retrying); si no, reintenta manualmente con POST /v1/payouts/instructions/{id}/retry

Un reintento manual no consume el tope de 5 días del reintento automático — puedes seguir reintentando a mano aunque la cadena automática ya se haya agotado. Ver Reintentar una dispersión fallida por saldo insuficiente.

Related


Did this page help you?