Saltar al contenido principal
Tu institución es un participante directo. Tiene presencia propia en el sistema nacional de liquidación de Pix (SPI), y este plugin es su camino hasta ahí. Un participante indirecto es una institución más pequeña sin conexión propia. Llega al SPI a través de la tuya. Es cliente de tu institución, no cliente del riel. Esa relación existe en el registro de tu tenant, no en el BACEN. La creas con POST /v1/indirects. El modelo directo e indirecto explica dónde se aloja el dinero de un participante indirecto: la cuenta de liquidación en el Banco Central, el límite en JD y la posición en tu ledger.
Esta página explica qué es una participación hospedada y cómo se comporta. Hospedar participantes indirectos cubre el aprovisionamiento: la clave de cifrado del secreto de entrega, la postura de hospedaje y la llamada de registro. Su numeración continúa desde la página de configuración. De esos pasos, solo la clave de cifrado rechaza todo registro por sí sola, así que aprovisiónala primero. La postura de hospedaje controla las rutas del dinero, no el registro, así que puedes registrar participantes antes de activarla.

Quién es responsable de qué


La mayoría de los errores operativos aquí vienen de buscar la respuesta en la columna equivocada. En la práctica, un Pix para un cliente de un participante indirecto se detiene en ti. Llega a una cuenta que representa la posición de esa institución. Tú no sabes, y no puedes saber, cuál de sus clientes es el dueño del dinero. El participante indirecto acredita a su cliente en su propio core, después de recibir tu aviso.

Qué crea registrar uno


Un registro escribe una fila, y el plugin también crea la posición de liquidación de esa institución en Midaz. Es una cuenta del ledger cuyo alias empieza con @pi_ y termina con el ISPB de la institución, el código de 8 letras mayúsculas o dígitos que BACEN asigna a cada participante. Un indirecto con ISPB 12345678 liquida en @pi_12345678. Esa posición mantiene el dinero de esa institución. La respuesta la devuelve como piAccountAlias. El plugin deriva el alias de ispb. Tú nunca lo eliges, y un cliente nunca crea la cuenta. Cada institución tiene una posición. Un crédito para un indirecto llega solo a la cuenta de ese indirecto. Por eso el dinero de una institución nunca se mezcla con el de otra, ni con el tuyo. El registro existe para ofrecer esa segregación.
El alias usa un guion bajo, no una barra: @pi_12345678. Midaz rechaza una barra en un alias de usuario, así que un nombre con forma de ruta no resuelve a nada.

Antes de registrar a nadie


La participación indirecta está apagada de forma predeterminada. El flag de systemplane por tenant plugin-br-pix-jd.indirects/enabled debe estar activo, y su valor predeterminado es false. El flag controla dos mitades, y se comportan distinto:
  • La API de gestión funciona con el flag apagado. Puedes registrar participantes, listarlos y leer uno antes de la habilitación. La incorporación previa al lanzamiento es deliberada.
  • Ningún dinero se mueve con el flag apagado. La resolución decide a qué indirecto pertenece un crédito entrante. Con el flag apagado, reporta la función como deshabilitada. Ningún crédito entrante va a una cuenta @pi, y ningún indirecto puede originar una orden saliente.
Dos operaciones se rechazan de plano mientras el flag está apagado, con 422 PIX-0111. Una es la escritura del certificado QR propio en PATCH /v1/indirects/{indirectId}. La otra es GET /v1/indirects/{indirectId}/jwks.
La lectura falla de forma cerrada. Cuando una ruta de dinero “vuelve a comportarse como directa” y nadie cambió la clave, esta es la causa. Revisa el systemplane.

Registrar uno


1

Activa la función para el tenant

Define plugin-br-pix-jd.indirects/enabled en true. El registro funciona sin ella, pero nada liquida hasta que esté activa.
2

POST /v1/indirects

Envía el name de la institución, su ispb, y un bloque delivery con endpointUrl y secret. La respuesta es 201. Trae indirectId, el identificador por el que se enruta cada llamada posterior, y piAccountAlias.
3

Nada por lo que hacer polling: un 201 significa listo

Cada verificación se ejecuta antes de que el plugin escriba la fila del registro. Primero viene la verificación de unicidad del ISPB, luego la verificación opcional en el directorio de JD, y luego la creación de la cuenta @pi_{ispb} en Midaz. Solo entonces el plugin escribe la fila, con estado ACTIVE. No hay estado intermedio ni nada que esperar.
4

Ante una falla, corrige la causa y vuelve a hacer POST

Un paso fallido responde con un error codificado que nombra el paso. El plugin no escribe ninguna fila en el registro, así que el ISPB sigue libre. Corrige la causa y vuelve a enviar el POST. Esa es la recuperación. No hay una ruta de reintento separada, porque no queda ninguna fila a medio escribir para reanudar.
El registro es atómico: un 201 significa listo, y una falla no deja fila en el registro. En un 201, status siempre es ACTIVE. No escribas un cliente que haga polling hasta que el participante se vuelva enrutable. Ese bucle espera un cambio que no puede ocurrir. Una falla puede dejar atrás solo la cuenta de liquidación, cuando ese paso tuvo éxito y uno posterior falló. El plugin no la revierte, y no causa ningún daño. Nada se enruta a una cuenta @pi sin una fila ACTIVE en el registro que apunte a ella. La creación de la cuenta es idempotente por alias, así que tu siguiente intento adopta la misma cuenta y no crea una segunda. Una falla nunca deja una fila en el registro ni un ISPB retenido. La respuesta todavía trae un objeto provisioning con un campo failedStep. Es heredado y siempre null para todo registro de este servicio, porque un registro fallido no deja fila. Los estados del ciclo de vida son ACTIVE, SUSPENDED y CLOSED.
PENDING_PROVISIONING es un estado retirado con un borde filoso. Ningún registro entra en él ahora. GET /v1/indirects todavía lo acepta como filtro, para que las filas de antes del registro atómico se sigan leyendo como tales. Pero ese estado no tiene transiciones de ciclo de vida en ninguna dirección. No puedes suspender ni cerrar una fila heredada. Toda acción sobre ella responde 409 PIX-0094.Esa fila es legible pero no enrutable, y todavía retiene su ISPB contra el índice de unicidad abierta. Por eso bloquea un registro nuevo de esa institución, y ninguna llamada a la API puede liberarla. Despejar una es una decisión de operador y de datos, y no es una operación de la API. Si tu tenant es anterior al registro atómico, ejecuta GET /v1/indirects?status=PENDING_PROVISIONING una vez para encontrar esas filas.

Cómo el dinero llega a un participante indirecto


Sigue un Pix entrante. El indirecto no llama a ningún endpoint en esta ruta. Tu plugin hace el trabajo.
  1. “¿Esta cuenta puede recibir?” Antes de que el Banco Central mueva el dinero, pregunta si la cuenta de destino acepta el crédito. La pregunta llega a ti, porque el participante indirecto no tiene línea al SPI. Con la función activada, el plugin responde por el cliente de un indirecto a partir del registro del indirecto, no a partir de la cuenta. Lee el registro cuando tu catálogo no atiende el ISPB del receptor. En una devolución, lo lee solo cuando tu catálogo no atiende a ninguna de las partes, y consulta el ISPB del pagador cuando el ISPB del receptor no nombra a ningún participante indirecto.
    • ACTIVE es válido. CLOSED responde AC07, y cualquier otro estado responde AC06.
    • El plugin no verifica que la cuenta exista en el core del indirecto. Un crédito para una cuenta que el indirecto no tiene llega a su posición @pi.
    • En la validación síncrona, cuando el plugin no puede leer el registro o la flag de la función, responde 500.
  2. Llega un Pix para una cuenta en la institución indirecta. Alcanza tu participación directa a través del webhook de cash-in del plugin.
  3. La resolución encuentra al indirecto a partir del ISPB en el bloque del receptor, en el registro de participación. Hay cuatro resultados, y solo el primero acredita a un indirecto:
    • El ISPB coincide con una participación ACTIVE. El crédito se contabiliza en el alias @pi_{ispb} de esa participación.
    • El ISPB es el tuyo. Es tu libro, y el CRM resuelve la cuenta como de costumbre.
    • El ISPB no coincide con nada, o coincide con una participación suspendida o cerrada. El plugin rechaza el crédito con 404 PIX-0119 y no registra nada. JD llama a esta ruta solo después de que el SPI liquidó el crédito, así que el dinero ya está en tu cuenta de liquidación en el Banco Central. Concilia ese endToEndId con tu extracto del SPI.
    • La resolución falla por infraestructura. El plugin no acredita ni rechaza. Responde un error, para que JD vuelva a entregar el crédito. Falla de forma cerrada a propósito y no supone nada sobre el dinero.
    El plugin nunca almacena en caché un fallo de resolución. Por eso un indirecto que acaba de activarse nunca queda oculto detrás de una respuesta negativa anterior.
  4. El crédito llega a @pi_{ispb}. El plugin registra el movimiento contra esa posición de liquidación y vincula su propio registro de transacción con el indirecto. Registra el aviso en la misma transacción de base de datos que el crédito. O existen ambos, o no existe ninguno.
  5. El plugin envía un aviso firmado al delivery.endpointUrl que registraste, con el payload de JD byte por byte. El plugin llama a la institución, no al revés.
  6. El participante indirecto acredita a su propio cliente, en su propio core.
El plugin no consulta al CRM en la ruta indirecta. El destino proviene del registro de participación, nunca de datos de quien llama. Eso es deliberado. Si un participante indirecto tuviera tu propio ISPB por error, los créditos para tu propio libro llegarían a su cuenta. Por eso la verificación “¿este ISPB es nuestro?” se ejecuta antes que la verificación “¿coincide con un participante indirecto?”.
El aviso lleva dos headers que la institución receptora verifica: La entrega es al menos una vez. El plugin puede volver a enviar el mismo aviso después de una falla reintentable, o después de una caída entre el envío y el registro contable. El endToEndId se mantiene igual entre intentos. Un receptor debe deduplicar por endToEndId y tratar un payload repetido como ya procesado.
El secret almacenado es de solo escritura. Cada lectura de un indirecto devuelve delivery.secret como ***, así que un secreto perdido se reemplaza con un PATCH, nunca se recupera.

Cuando el aviso nunca llega


El envío puede fallar de forma definitiva. Los reintentos se agotan, o el endpoint responde 4xx. Después de eso, el plugin marca el aviso como inválido y nunca lo vuelve a intentar. El dinero está correcto de cualquier forma. El movimiento es real, y la institución no lo sabe. GET /v1/indirects/{indirectId}/transactions cierra esa brecha. Devuelve los movimientos que se liquidaron en la cuenta @pi_{ispb} de un indirecto. Por eso es el registro autoritativo, no el aviso. Una institución que lo lee según una programación ya no depende de la entrega. También es la única forma de volver a leer una ventana después de una interrupción propia. Listar las transacciones de un participante indirecto da las reglas que hacen correcta una conciliación. Cubren la ventana since semiabierta obligatoria, los centavos como enteros, la paginación por cursor de más antiguo a más reciente y el recorte silencioso de limit. Una lectura del feed no cambia nada y no vuelve a enviar ningún aviso.

Devolver un Pix que recibió un participante indirecto


La devolución de un Pix que llegó a la posición de un participante indirecto sale de ese indirecto. Envía POST /v1/refunds con el indirectId de ese participante. La devolución entonces debita su posición @pi_{ispb}. No necesita accountId: quien devuelve es el receptor que nombra el Pix original. Con un socio detrás de la solicitud, un accountId responde 400 PIX-0061. El indirectId debe nombrar exactamente al participante que recibió el Pix original. El plugin no mueve dinero, y responde:
  • 422 PIX-0135 cuando el Pix original llegó a la posición de un indirecto, y la devolución no tiene indirectId.
  • 404 PIX-0147 cuando la devolución nombra a otro participante indirecto, o cuando el Pix original llegó a una cuenta directa y la devolución tiene un indirectId.
Después, el indirectId pasa las mismas verificaciones que en una orden de pago: 422 PIX-0111 cuando la función está apagada, 404 PIX-0095 para un participante desconocido, y 422 PIX-0100 cuando la participación no está en ACTIVE.

Hospedar el código QR bajo el certificado propio de la institución


De forma predeterminada, el plugin publica el código QR dinámico de un indirecto bajo tu participación directa. Un indirecto puede publicar el suyo propio en su lugar. Define qrCertificate.ownCertificate con un publicBaseUrl en PATCH /v1/indirects/{indirectId}. La institución entonces sirve el documento firmado desde su propio host. La institución no puede producir las claves de validación por sí misma. JDPI mantiene el certificado y hace la firma. GET /v1/indirects/{indirectId}/jwks devuelve el JWK Set de ese indirecto. La institución lo publica en su propio host, y los PSP pagadores validan contra él. Obtenlo cuando configures el certificado, y de nuevo después de cada rotación. El estado del ciclo de vida deliberadamente no controla esa lectura. Un indirecto suspendido o cerrado todavía tiene códigos QR activos en circulación. Sin la clave, su validación se rompería.

Leer el registro


Listar participantes indirectos da las reglas de paginación por cursor y el recorte silencioso de limit. Un GET por id, y la lista, siempre muestran la verdad actual. Las rutas del dinero leen una proyección en caché. Cada réplica puede tardar hasta el TTL de la caché de resolución en ver una suspensión. El TTL es de 30 segundos de forma predeterminada. El bloqueo del ledger de suspend se aplica en el momento de la llamada.

Suspender, reactivar y cerrar


PATCH /v1/indirects/{indirectId} lleva como máximo un action de ciclo de vida: suspend, reactivate o close. Cada uno escribe tanto el ledger como el registro.
  • suspend detiene tanto el envío como la recepción en la cuenta @pi del indirecto. El ledger mismo rechaza el dinero de ese participante. Por eso una suspensión se mantiene incluso en una réplica cuya caché de resolución está desactualizada. reactivate vuelve a habilitar ambos.
  • close es permanente. CLOSED es terminal, y después el plugin rechaza también todo cambio de campo en la fila.
Un cierre no elimina nada. El plugin bloquea la cuenta @pi de forma permanente pero la conserva. Un registro posterior del mismo ISPB reutiliza esa cuenta. La cuenta queda desbloqueada, activa de nuevo, y conserva su historial contable. Un PATCH con solo campos y ningún action cambia esos campos. Un PATCH vacío no cambia nada y devuelve la fila actual. No es un error.
No hay DELETE en /v1/indirects, y CLOSED es terminal. La única salida del registro es close, que conserva la fila y la cuenta. Un registro hecho por error en un tenant en vivo permanece para siempre en el registro de ese tenant. Su ISPB queda retenido contra la restricción de unicidad abierta hasta que lo cierres. Cada código de institución que gastas es permanente. Verifica name, ispb y el endpoint de entrega antes de llamar.

Qué sale mal


Las páginas de operación listan cada rechazo de este registro, con su estado y su condición exacta. También dicen qué escribió o no escribió el plugin cuando rechazó. Consulta Registrar un participante indirecto, Actualizar un participante indirecto, Obtener un participante indirecto, Listar participantes indirectos, Obtener el JWK Set de un participante indirecto y Listar las transacciones de un participante indirecto. La lista de errores de Pix JD tiene cada código, con el detail exacto de cada uno.
El estado te dice si conviene reintentar, no la familia del código. PIX-0107 y PIX-0123 son la misma condición, vista de dos formas. Un 409 significa que el riel leyó la configuración y encontró que la clave no existe. Solo un operador puede cambiar eso, así que un reintento se repite para siempre. Un 503 significa que la lectura no se completó. El riel no sabe que la clave está ausente, así que un reintento es correcto. Ninguno de los dos almacena nada.

A dónde ir después