Winal Connect

MODO PRUEBA MARKETPLACE / PLATAFORMA

Si tu negocio ES una plataforma — un marketplace, un ISV, una app que cobra a nombre de otros negocios — Winal Connect te deja cobrar a tu comprador, partir ese cobro entre tu comisión y tus sub-comercios, y dispersar cada porción a la CLABE de cada quien. Mismo principio de toda la API (ADR-0001, sin custodia): el dinero nunca pasa por una cuenta de Winal — se liquida a tu cuenta de plataforma y de ahí se dispersa a cada sub-comercio por el riel de dispersión (Payouts), igual que Stripe Connect pero sobre rieles mexicanos (SPEI vía STP).

1

Cobras a tu comprador

Un payment_intent normal — nada cambia en confirm.

succeeded
2

Partes el cobro (split)

Tu comisión (application_fee_minor) se queda contigo; el resto se vuelve una obligación con el sub.

split
3

Dispersas al sub-comercio

Un payout a la CLABE del sub-comercio salda la obligación — evidencia del proveedor, nunca timeout.

paid
Requisito: el sub-comercio debe estar aprobado
Antes de poder ligar una cuenta Connect necesitas una solicitud de Onboarding en estado approved — es donde vive la CLABE de liquidación del sub-comercio y su verificación KYB. Sin eso, POST /v1/connect/accounts responde connect.application_not_approved.

1. Liga un sub-comercio aprobado

bash
curl -s https://api.winal.com.mx/v1/connect/accounts \
  -H "Authorization: Bearer $SK" \
  -H "Content-Type: application/json" \
  -d '{ "onboarding_application_id": "0732573a-5567-4b2a-87b6-8721654c5060" }'
201 · respuesta real
{
  "id": "0d0de738-0133-4625-97e2-9c285ae20f9e",
  "object": "connect_account",
  "onboarding_application_id": "0732573a-5567-4b2a-87b6-8721654c5060",
  "settlement_clabe_masked": "**** **** **** 0004",
  "sub_merchant_name": "Sub Comercio Sim SA de CV",
  "status": "active",
  "connector_key": "sim",
  "livemode": false,
  "created_at": "2026-07-08T06:49:58.292982+00:00"
}

connector_key es opcional (por defecto stp, el riel de dispersión real); usa "sim" mientras pruebas sin cuenta STP — igual que en Payouts. La CLABE del sub jamás se expone completa. Ligar la misma solicitud dos veces da 409 connect.account_conflict.

2. Marca un cobro como "de plataforma"

Hay dos formas — ambas soportadas hoy, elige según tu caso:

Vía A — metadata en el intent (recomendada, automática)

Al crear el payment_intent, pon en metadata el connect_account_id del sub-comercio y, opcional, tu application_fee_minor (ambos como strings, igual que cualquier valor de metadata). Cubre el caso de un solo sub-comercio por cobro.

bash
curl -s https://api.winal.com.mx/v1/payment_intents \
  -H "Authorization: Bearer $SK" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_minor": 100000,
    "currency": "MXN",
    "metadata": {
      "connect_account_id": "0d0de738-0133-4625-97e2-9c285ae20f9e",
      "application_fee_minor": "10000"
    }
  }'

Confirma el intent como cualquier otro (tok_sim_ok en pruebas). En cuanto llega a succeeded, el Worker lee la metadata y arma el split automáticamente — sin llamada extra de tu parte. Es asíncrono: espera unos segundos (o escucha el webhook) antes de consultar GET /v1/connect/transfers.

Vía B — endpoint manual (post-cobro, múltiples sub-comercios)

POST /v1/connect/transfers reparte un cobro ya exitoso entre varios sub-comercios a la vez. Exige Idempotency-Key (compromete dinero). La regla de cuadre es estricta: application_fee_minor + Σ splits[].amount_minor debe ser exactamente igual a charge_amount_minor, en la misma moneda — si no, 400 connect.split_mismatch.

bash
curl -s https://api.winal.com.mx/v1/connect/transfers \
  -H "Authorization: Bearer $SK" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "payment_intent_id": "1a1a1190-e378-4731-9c7f-454b3815e80f",
    "charge_amount_minor": 50000,
    "application_fee_minor": 2500,
    "currency": "MXN",
    "splits": [
      { "connect_account_id": "8c90e712-b8d0-4158-81f8-38a7c1430fcf", "amount_minor": 47500 }
    ]
  }'

splits admite más de una entrada para repartir un mismo cobro entre varios sub-comercios (un solo application_fee_minor para toda la plataforma). Un cobro solo se reparte una vez — reintentar sobre el mismo payment_intent_id con datos distintos falla antes de tocar dinero dos veces.

3. Dispersión a cada sub-comercio

bash
curl -s https://api.winal.com.mx/v1/connect/transfers \
  -H "Authorization: Bearer $SK"
200 · respuesta real
{
  "object": "list",
  "data": [
    {
      "id": "53fb6fd5-92c8-4e13-93a1-f01aefb21ce8",
      "object": "connect_transfer",
      "payment_intent_id": "aaab3de3-3cfe-4d55-9bbd-24a89977abf7",
      "charge_amount_minor": 100000,
      "application_fee_minor": 10000,
      "currency": "MXN",
      "status": "split",
      "created_at": "2026-07-08T06:50:09.078051+00:00",
      "splits": [
        {
          "id": "09702eba-5dc9-485f-bf64-083fec7f7078",
          "connect_account_id": "0d0de738-0133-4625-97e2-9c285ae20f9e",
          "amount_minor": 90000,
          "status": "dispersing",
          "payout_id": "fa888dfc-8457-41b7-9726-4623e31bd5e6"
        }
      ]
    }
  ]
}

Cada porción (splits[]) es en el fondo un payout — sigue su propio ciclo de vida (pending → dispersing → paid, o failed). Cuando el payout llega a paid, la obligación con el sub queda saldada. Consulta GET /v1/payouts/{payout_id} para ver el detalle de la dispersión en curso.

Estado del connect_transferSignifica
splitEl reparto ya se contabilizó (tu comisión + la(s) porción(es) del/los sub(s)); la dispersión está en curso.
settledTodas las porciones ya se pagaron al sub-comercio correspondiente.
Estado de una porción (splits[].status)Significa
pendingContabilizada, dispersión aún no ordenada.
dispersingEl payout ya se ordenó; en espera de evidencia del proveedor (regla 5 — nunca por timeout).
paidEl sub-comercio ya recibió su porción.
failedLa dispersión falló (ver GET /v1/payouts/{payout_id} para el motivo).
En modo prueba, la dispersión se queda en dispersing
El split y el payout se crean correctamente incluso con el conector sim, pero Sim no tiene forma de forzar el pago final por HTTP (solo lo resuelven credenciales STP reales en producción, o las utilerías internas de los tests). Es exactamente el comportamiento correcto de "esperando evidencia del proveedor" — no un bug: el reparto y la contabilidad ya ocurrieron, la liquidación final del riel es lo único gated por el trámite STP del dueño.

Endpoints

MétodoRutaNotas
POST/v1/connect/accountsLiga un sub-comercio aprobado.
GET/v1/connect/accounts?limit= opcional.
GET/v1/connect/accounts/{id}404 connect.account_not_found si no existe.
POST/v1/connect/transfersSplit manual; requiere Idempotency-Key.
GET/v1/connect/transfers?limit= opcional.
GET/v1/connect/transfers/{id}404 connect.transfer_not_found si no existe.

Errores de connect

HTTPcodeCausa
400connect.invalid_application_idonboarding_application_id ausente o no es un uuid.
404connect.application_not_foundLa solicitud de Onboarding referida no existe.
400connect.application_not_approvedLa solicitud existe pero no está approved.
409connect.account_conflictEsa solicitud ya está ligada a una cuenta Connect.
404connect.account_not_foundEl id de la cuenta (o un connect_account_id dentro de splits) no existe.
400connect.account_suspendedLa cuenta Connect no está activa.
400connect.invalid_payment_intentpayment_intent_id ausente o no es un uuid.
400connect.invalid_chargeFalta charge_amount_minor positivo o currency.
400connect.invalid_currencyMoneda ISO 4217 inválida.
400connect.missing_allocationssplits vacío o ausente.
400connect.invalid_allocationUna porción trae connect_account_id inválido o amount_minor no positivo.
400connect.split_mismatchapplication_fee_minor + Σ splits no cuadra exactamente con charge_amount_minor.
404connect.transfer_not_foundEl id del transfer no existe.