Skip to main content
Tu institución es un participante directo: está presente en el sistema nacional de liquidación de Pix (SPI) por derecho propio, y este plugin es cómo llega ahí. Un participante indirecto es una institución más pequeña que no tiene conexión propia y llega al SPI a través de la tuya. Es cliente de tu institución, no cliente del riel. Nada de esa relación existe en el BACEN. Existe en el registro de tu tenant, y la creas con POST /v1/indirects.
Esta página es lo que es una participación hospedada y cómo se comporta. El aprovisionamiento — la clave de cifrado del secreto de entrega, la postura de hospedaje y la llamada de registro misma — es Hospedar participantes indirectos, que continúa la numeración de la página de configuración. De esos, solo la clave de cifrado rechaza todo registro por sí sola — 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é


Casi todo malentendido operativo aquí es alguien buscando la respuesta en la columna equivocada. En la práctica: cuando llega un Pix para un cliente de un participante indirecto, el dinero se detiene en ti, en 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. El participante indirecto acredita a su cliente en su propio core, después de recibir tu aviso.

Qué crea registrar uno


Registrar un participante indirecto hace más que escribir una fila. El plugin también crea la posición de liquidación de esa institución en Midaz: una cuenta del ledger cuyo alias es @pi_ seguido del ISPB de la institución, el código de ocho dígitos que BACEN asigna a cada participante. Un indirecto con ISPB 12345678 liquida en @pi_12345678. Esa posición es donde se aloja el dinero de esa institución. La respuesta la devuelve como piAccountAlias, y el alias se deriva de ispb — tú nunca lo eliges, y un cliente nunca crea la cuenta. Una posición por institución es precisamente el objetivo. Un crédito que llega para un indirecto aterriza solo en la cuenta de ese indirecto, así que el dinero de una institución nunca se mezcla con el de otra ni con el tuyo. Esa segregación es la propiedad que el registro existe para ofrecer.
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. Vale la pena saber qué controla realmente el flag, porque las dos mitades 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 — el paso que decide a qué indirecto pertenece un crédito entrante — reporta la función como deshabilitada, así que un crédito entrante nunca se enruta 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: la escritura del certificado QR propio en PATCH /v1/indirects/{indirectId}, y GET /v1/indirects/{indirectId}/jwks. Una cosa sigue funcionando con el flag apagado que podrías esperar que estuviera controlada: la cola de excepciones, para que un crédito ya estacionado para revisión nunca quede atrapado detrás de un interruptor.
La lectura falla de forma cerrada. Si el systemplane no responde, si el valor no resuelve, o si llega con el tipo equivocado, el plugin lee el flag como apagado — nunca activo por accidente. Una ruta de dinero que “volvió a comportarse como una directa” sin que nadie haya tocado la clave es este mecanismo. 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 que contenga endpointUrl (una URL HTTPS) y secret. La respuesta es 201 y 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 paso se ejecuta antes de escribir la fila del registro: la verificación de unicidad del ISPB, la verificación opcional en el directorio de JD, y luego la creación de la cuenta @pi_{ispb} en Midaz. Solo entonces se escribe la fila, y se escribe como 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, y no se escribe ninguna fila en el registro, así que el ISPB sigue libre. Corrige la causa y vuelve a hacer POST — volver a enviarlo es la recuperación, y no hay una ruta de reintento separada porque nada quedó a medio escribir para reanudar.
El registro es atómico: un 201 significa listo, y una falla no deja fila en el registro. status en un 201 siempre es ACTIVE. Si estás escribiendo un cliente que hace polling hasta que el participante se vuelve enrutable, elimina ese bucle — está esperando una transición que no puede ocurrir. Lo único que una falla puede dejar atrás es la cuenta de liquidación, cuando ese paso tuvo éxito y uno posterior no. No se revierte, y es inofensiva: nada se enruta a una cuenta @pi sin una fila ACTIVE en el registro que apunte a ella, y crear la cuenta es idempotente por alias, así que tu siguiente intento adopta la misma cuenta en lugar de crear una segunda. Lo que una falla nunca deja es una fila en el registro o un ISPB retenido. La respuesta todavía trae un objeto provisioning con un campo failedStep. Es heredado y siempre null para todo lo que este servicio registra, porque un registro fallido no deja fila que lleve un marcador. Los estados del ciclo de vida son ACTIVE, SUSPENDED y CLOSED.
PENDING_PROVISIONING es un estado retirado con un borde filoso. Ya no se registra nada en él, y todavía se acepta como filtro en GET /v1/indirects para que las filas escritas antes de que el registro se volviera atómico se sigan leyendo como tales. Pero ese estado no tiene transiciones de ciclo de vida en ninguna dirección — una fila heredada no puede suspenderse ni puede cerrarse; toda acción sobre ella se rechaza con 409 PIX-0094.Esa fila es legible, no enrutable, y todavía retiene su ISPB contra el índice de unicidad abierta. Así que bloquea cualquier 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, deliberadamente no una operación de la API. Si heredaste un tenant de antes de este cambio, ejecuta GET /v1/indirects?status=PENDING_PROVISIONING una vez para averiguar si tienes alguna.

Cómo el dinero llega a un participante indirecto


Sigue un Pix entrante. Nada en esta ruta es un endpoint que el indirecto llama; es tu plugin el que hace el trabajo.
  1. “¿Esta cuenta puede recibir?” Antes de mover el dinero, el Banco Central 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.
  2. Llega un Pix para una cuenta que se mantiene en la institución indirecta y alcanza tu participación directa, entregado al webhook de cash-in del plugin.
  3. La resolución encuentra al indirecto por el ISPB en el bloque del receptor, buscado en el registro de participación. Cuatro resultados, y solo el primero acredita a un indirecto:
    • coincide con una participación ACTIVE, así que el crédito se contabiliza en el alias @pi_{ispb} de esa participación;
    • es tu propio ISPB, así que es tu libro, y el CRM resuelve la cuenta como de costumbre;
    • no coincide con nada, o coincide con una participación suspendida o cerrada, así que el crédito queda estacionado para que un operador lo revise;
    • la resolución falla por infraestructura, así que el plugin no acredita ni estaciona. Falla de forma cerrada a propósito: nada relacionado con dinero se supone.
    Un fallo de resolución nunca se almacena en caché, así que un indirecto que acaba de activarse no queda ensombrecido por una respuesta negativa anterior.
  4. El crédito se contabiliza en @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. El aviso se registra en la misma transacción de base de datos que el crédito — o existen ambos, o no existe ninguno.
  5. Se envía un aviso firmado al delivery.endpointUrl que registraste, que lleva el payload de JD byte por byte. Es el plugin el que llama a la institución, no al revés.
  6. El participante indirecto acredita a su propio cliente, en su propio core.
El CRM no se consulta en la ruta indirecta. El destino proviene del registro de participación, nunca de datos que envió quien llama. Eso es deliberado: si un participante indirecto quedara mal registrado con tu propio ISPB, los créditos destinados a 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 mismo aviso puede reenviarse después de una falla reintentable o una caída entre el envío y el registro contable, y el endToEndId es estable entre intentos — así que 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 permanente — se agotan los reintentos, o el endpoint responde 4xx, después de lo cual el aviso se marca inválido y no se vuelve a intentar. El dinero se contabiliza correctamente de cualquier forma. La consecuencia es más puntual y peor de lo que suena: el movimiento es real, y la institución no lo sabe. GET /v1/indirects/{indirectId}/transactions es lo que cierra esa brecha. Devuelve los movimientos que realmente se liquidaron en la cuenta @pi_{ispb} de un indirecto, lo que la convierte en el registro autoritativo en lugar del aviso. Barrerla según una programación es cómo una institución deja de depender de que la entrega haya funcionado, y también es la única forma de releer una ventana después de una interrupción propia. Las reglas de ventana, paginación y unidad que deciden si una conciliación es correcta — la ventana since semiabierta obligatoria, los centavos como enteros, la paginación por cursor de más antiguo a más reciente, el limit recortado en silencio — son el contrato de Listar las transacciones de un participante indirecto. Leer el feed no cambia nada y no reenvía ningún aviso.

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


De forma predeterminada, un código QR dinámico emitido para un indirecto se firma y se publica 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}, y el documento firmado se sirve desde el host propio de la institución. La institución no puede producir las claves de validación por sí misma, porque JDPI mantiene el certificado y hace la firma. GET /v1/indirects/{indirectId}/jwks devuelve el JWK Set de ese indirecto para que la institución pueda publicarlo en su propio host, para que los PSP pagadores validen contra él. Obtenlo cuando configures el certificado y de nuevo cada vez que rote. 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, y retener la clave rompería su validación.

Leer el registro


Los filtros son status e ispb (8 dígitos, exactos); las reglas de paginación por cursor y el limit recortado en silencio son el contrato de Listar participantes indirectos. Un GET por id, y la lista, son siempre la verdad actual. Las rutas del dinero leen una proyección en caché, así que una suspensión puede tardar hasta el TTL de la caché de resolución — 30 segundos de forma predeterminada — en verse en cada réplica. El bloqueo del ledger de suspend ya se aplicó 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 — y cada uno escribe tanto el ledger como el registro.
  • suspend deshabilita tanto el envío como la recepción en la cuenta @pi del indirecto. El ledger mismo rechaza el dinero de ese participante, por lo que una suspensión se mantiene incluso en una réplica cuya caché de resolución todavía está desactualizada. reactivate vuelve a habilitar ambos.
  • close es irreversible. CLOSED es terminal, y después se rechaza también todo cambio de campo en la fila.
Un cierre nunca elimina nada. La cuenta @pi queda bloqueada de forma permanente pero se conserva, así que un registro posterior del mismo ISPB reutiliza la cuenta histórica — desbloqueada, reactivada, y con su historial contable intacto. Un PATCH que lleva solo campos y ningún action cambia esos campos. Un PATCH que no lleva nada es un no-op que devuelve la fila actual, no 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 indirecto registrado por error contra un tenant activo permanece para siempre en el registro de ese tenant, y su ISPB queda retenido contra la restricción de unicidad abierta hasta que se cierra. Cada código de institución que gastas es permanente — verifica name, ispb y el endpoint de entrega antes de llamar.

Qué sale mal


Todo rechazo que este registro puede responder — su estado, su condición exacta, y qué se escribió o no cuando se disparó — está en las páginas de operación: 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. El catálogo completo, con el detail exacto que lleva cada código, es la lista de errores de Pix JD.
Si conviene reintentar lo decide el estado, no la familia del código. PIX-0107 y PIX-0123 son la misma condición vista dos veces: un 409 significa que el riel leyó la configuración y estableció que la clave está ausente — solo un operador cambia eso, así que reintentar se repite para siempre. Un 503 significa que la lectura misma no se completó, así que no se sabe que la clave falte, y reintentar es correcto. Ninguno de los dos almacena nada.

A dónde ir después