Skip to main content
A sua instituição é um participante direto: ela está presente no sistema de liquidação nacional do Pix (SPI) por direito próprio, e este plugin é como ela chega lá. Um participante indireto é uma instituição menor que não tem conexão própria e chega ao SPI pela sua. Ele é cliente da sua instituição, não cliente do trilho. Nada dessa relação existe no BACEN. Ela existe no registro do seu tenant, e você a cria com POST /v1/indirects.
Esta página descreve o que uma participação hospedada é e como ela se comporta. O provisionamento — a chave de criptografia do segredo de entrega, a postura de hospedagem e a própria chamada de cadastro — é Hospedar participantes indiretos, que dá continuidade à numeração da página de configuração. Desses, apenas a chave de criptografia recusa todo cadastro sozinha — provisione-a primeiro. A postura de hospedagem controla os caminhos do dinheiro, não o registro, então você pode cadastrar participantes antes de ativá-la.

Quem é responsável pelo quê


Quase todo mal-entendido operacional aqui é alguém procurando a resposta na coluna errada. Na prática: quando chega um Pix para um cliente de um participante indireto, o dinheiro para com você, em uma conta que representa a posição daquela instituição. Você não sabe — e não pode saber — qual dos clientes dela é o dono. O participante indireto credita o cliente dele no próprio core, depois de receber o seu aviso.

O que cadastrar um indireto cria


Cadastrar um participante indireto faz mais do que gravar uma linha. O plugin também cria a posição de liquidação dessa instituição no Midaz: uma conta do ledger cujo alias é @pi_ seguido do ISPB da instituição, o código de oito dígitos que o BACEN atribui a cada participante. Um indireto no ISPB 12345678 liquida em @pi_12345678. É nessa posição que o dinheiro dessa instituição fica. A resposta a retorna como piAccountAlias, e o alias deriva de ispb — você nunca o escolhe, e um cliente nunca cria a conta. Uma posição por instituição é todo o propósito disso. Um crédito que chega para um indireto cai apenas na conta desse indireto, então o dinheiro de uma instituição nunca se mistura com o de outra, nem com o seu. Essa segregação é a propriedade que o registro existe para garantir.
O alias usa underscore, não barra: @pi_12345678. O Midaz rejeita barra em um alias de usuário, então um nome no formato de caminho não resolve a nada.

Antes de cadastrar alguém


A participação indireta vem desligada por padrão. A flag do systemplane por tenant plugin-br-pix-jd.indirects/enabled deve estar ligada, e o valor padrão dela é false. Vale saber o que a flag realmente controla, porque as duas metades se comportam de formas diferentes:
  • A API de gerenciamento funciona com a flag desligada. Você pode cadastrar participantes, listá-los e ler um deles antes da habilitação. O onboarding antes do go-live é proposital.
  • Nenhum dinheiro se move com a flag desligada. A resolução — a etapa que decide a qual indireto um crédito recebido pertence — reporta a funcionalidade como desabilitada, então um crédito recebido nunca é roteado para uma conta @pi, e nenhum indireto pode originar uma ordem de saída.
Duas operações recusam de imediato enquanto a flag está desligada, com 422 PIX-0111: a escrita de certificado de QR próprio em PATCH /v1/indirects/{indirectId}, e GET /v1/indirects/{indirectId}/jwks. Uma coisa continua funcionando com a flag desligada que você poderia esperar que estivesse controlada: a fila de exceções, para que um crédito já retido para triagem nunca fique preso atrás de uma chave.
A leitura falha fechada. Se o systemplane não responde, se o valor não resolve, ou se ele volta com o tipo errado, o plugin lê a flag como desligada — nunca ligada por acidente. Um caminho do dinheiro que “voltou a se comportar como um direto” sem que ninguém tenha tocado na chave é esse mecanismo. Olhe o systemplane.

Cadastrando um indireto


1

Ative a funcionalidade para o tenant

Defina plugin-br-pix-jd.indirects/enabled como true. O cadastro funciona sem isso, mas nada liquida até que esteja ligada.
2

POST /v1/indirects

Envie o name da instituição, o ispb dela, e um bloco delivery contendo endpointUrl (uma URL HTTPS) e secret. A resposta é 201 e traz indirectId — o identificador pelo qual toda chamada posterior é roteada — e piAccountAlias.
3

Nada para consultar em loop — um 201 significa pronto

Toda etapa roda antes que a linha do registro seja gravada: a verificação de unicidade do ISPB, a verificação opcional no diretório da JD, depois a criação da conta @pi_{ispb} no Midaz. Apenas então a linha é gravada, e ela é gravada como ACTIVE. Não há estado intermediário nem nada a esperar.
4

Em caso de falha, corrija a causa e envie o POST de novo

Uma etapa que falha responde com um erro codificado que nomeia a etapa, e nenhuma linha do registro é gravada, então o ISPB continua livre. Corrija a causa e envie o POST de novo — reenviar é a recuperação, e não há uma rota de nova tentativa separada porque nada ficou parcialmente escrito para retomar.
O cadastro é atômico: um 201 significa pronto, e uma falha não deixa nenhuma linha no registro. O status em um 201 é sempre ACTIVE. Se você está escrevendo um cliente que fica consultando em loop até o participante ficar roteável, apague esse loop — ele está esperando por uma transição que não pode acontecer. A única coisa que uma falha pode deixar para trás é a conta de liquidação, quando essa etapa teve sucesso e uma posterior não. Ela não é desfeita, e é inofensiva: nada é roteado para uma conta @pi sem uma linha ACTIVE no registro apontando para ela, e criar a conta é idempotente por alias, então a próxima tentativa adota a mesma conta em vez de criar uma segunda. O que uma falha nunca deixa é uma linha no registro ou um ISPB retido. A resposta ainda traz um objeto provisioning com um campo failedStep. Ele é legado e sempre null para tudo o que este serviço cadastra, porque um cadastro malsucedido não deixa nenhuma linha para carregar um marcador. Os estados do ciclo de vida são ACTIVE, SUSPENDED e CLOSED.
PENDING_PROVISIONING é um estado aposentado com uma aresta afiada. Nada mais é cadastrado nele, e ele ainda é aceito como filtro em GET /v1/indirects para que linhas gravadas antes de o cadastro se tornar atômico continuem sendo lidas como elas mesmas. Mas esse estado não tem transições de ciclo de vida em nenhuma direção — uma linha legada não pode ser suspensa nem encerrada; toda ação sobre ela é recusada com 409 PIX-0094.Uma linha assim é legível, não roteável, e ainda mantém o ISPB dela contra o índice de unicidade aberto. Então ela bloqueia qualquer novo cadastro dessa instituição, e nenhuma chamada de API consegue liberá-la. Limpar uma é uma decisão de operador e de dados, propositalmente não uma operação de API. Se você herdou um tenant de antes dessa mudança, rode GET /v1/indirects?status=PENDING_PROVISIONING uma vez para descobrir se você tem alguma.

Como o dinheiro chega a um participante indireto


Acompanhe um Pix recebido. Nada nesse caminho é um endpoint que o indireto chama; é o seu plugin que faz o trabalho.
  1. “Esta conta pode receber?” Antes de mover o dinheiro, o Banco Central pergunta se a conta de destino aceita o crédito. A pergunta chega até você, porque o participante indireto não tem linha própria com o SPI.
  2. Um Pix chega para uma conta mantida na instituição indireta e alcança sua participação direta, entregue ao webhook de cash-in do plugin.
  3. A resolução encontra o indireto pelo ISPB no bloco do recebedor, consultado no registro de participação. Quatro desfechos, e apenas o primeiro credita um indireto:
    • corresponde a uma participação ACTIVE, então o crédito é lançado no alias @pi_{ispb} dessa participação;
    • é o seu próprio ISPB, então é o seu livro, e o CRM resolve a conta como de costume;
    • não corresponde a nada, ou corresponde a uma participação suspensa ou encerrada, então o crédito fica retido para um operador fazer a triagem;
    • a resolução falha por infraestrutura, então o plugin nem credita nem retém. Ela falha fechada de propósito: nada relacionado a dinheiro é adivinhado.
    Uma falha de resolução nunca é armazenada em cache, então um indireto que acabou de ficar ativo não fica sombreado por uma resposta negativa anterior.
  4. O crédito é lançado em @pi_{ispb}. O plugin registra a movimentação contra essa posição de liquidação e vincula seu próprio registro de transação ao indireto. O aviso é gravado na mesma transação de banco de dados que o crédito — ou os dois existem, ou nenhum existe.
  5. Um aviso assinado é enviado ao delivery.endpointUrl que você cadastrou, carregando o payload da JD byte a byte. Aqui é o plugin chamando a instituição, não o contrário.
  6. O participante indireto credita o próprio cliente, no próprio core.
O CRM não é consultado no caminho indireto. O destino vem do registro de participação, nunca dos dados que o chamador enviou. Isso é proposital: se um participante indireto fosse cadastrado por engano com o seu próprio ISPB, créditos destinados ao seu próprio livro cairiam na conta dele. Por isso a verificação “esse ISPB é nosso?” roda antes da verificação “corresponde a um participante indireto?”.
O aviso carrega dois headers que a instituição receptora verifica: A entrega é pelo menos uma vez. O mesmo aviso pode ser reenviado após uma falha que permite nova tentativa, ou uma queda entre o envio e o lançamento contábil, e o endToEndId é estável entre as tentativas — então um receptor deve deduplicar pelo endToEndId e tratar um payload repetido como já processado.
O secret armazenado é apenas-escrita. Toda leitura de um indireto retorna delivery.secret como ***, então um secret perdido é substituído com um PATCH, nunca recuperado.

Quando o aviso nunca chega


O envio pode falhar de forma permanente — as tentativas se esgotam, ou o endpoint responde 4xx, depois do que o aviso é marcado como inválido e nunca mais tentado. O dinheiro é lançado corretamente de qualquer forma. A consequência é mais restrita e pior do que parece: a movimentação é real, e a instituição não sabe dela. GET /v1/indirects/{indirectId}/transactions é o que fecha essa lacuna. Ele retorna as movimentações que realmente se liquidaram na conta @pi_{ispb} de um indireto, o que faz dele o registro definitivo, não o aviso. Varrer esse endpoint em um agendamento é como uma instituição para de depender de a entrega ter funcionado, e é também a única forma de reler uma janela depois de uma indisponibilidade própria. As regras de janela, paginação e unidade que decidem se uma conciliação está correta — a janela since semiaberta obrigatória, centavos inteiros, paginação por cursor do mais antigo para o mais novo, o limit limitado silenciosamente — são o contrato de Listar as transações de um participante indireto. Ler o feed não muda nada e não reenvia nenhum aviso.

Hospedando o QR code sob o certificado próprio da instituição


Por padrão, um QR code dinâmico emitido para um indireto é assinado e publicado sob a sua participação direta. Um indireto pode publicar o próprio em vez disso: defina qrCertificate.ownCertificate com um publicBaseUrl em PATCH /v1/indirects/{indirectId}, e o documento assinado passa a ser servido a partir do próprio host da instituição. A instituição não pode produzir as chaves de validação sozinha, porque a JDPI mantém o certificado e faz a assinatura. GET /v1/indirects/{indirectId}/jwks retorna o JWK Set desse indireto para que a instituição possa publicá-lo no próprio host, para que PSPs pagadores validem contra ele. Busque-o quando configurar o certificado e de novo sempre que ele rotacionar. O status do ciclo de vida deliberadamente não controla essa leitura: um indireto suspenso ou encerrado ainda tem QR codes ativos em circulação, e reter a chave quebraria a validação deles.

Lendo o registro


Os filtros são status e ispb (8 dígitos, exato); as regras de paginação por cursor e o limit limitado silenciosamente são o contrato de Listar participantes indiretos. Um GET por id, e a lista, são sempre a verdade atual. Os caminhos do dinheiro leem uma projeção em cache, então uma suspensão pode levar até o TTL do cache de resolução — 30 segundos por padrão — para ser vista por todas as réplicas. O bloqueio do ledger vindo de suspend já se aplica no momento da chamada.

Suspendendo, reativando e encerrando


PATCH /v1/indirects/{indirectId} carrega no máximo uma action de ciclo de vida — suspend, reactivate ou close — e cada uma escreve tanto no ledger quanto no registro.
  • suspend desabilita tanto o envio quanto o recebimento na conta @pi do indireto. O próprio ledger recusa o dinheiro desse participante, por isso uma suspensão vale mesmo em uma réplica cujo cache de resolução ainda está desatualizado. reactivate reabilita os dois.
  • close é irreversível. CLOSED é terminal, e depois disso toda mudança de campo na linha também é recusada.
Um encerramento nunca apaga nada. A conta @pi fica permanentemente bloqueada, mas é mantida, então um novo cadastro posterior do mesmo ISPB reaproveita a conta histórica — desbloqueada, reativada, e com o histórico contábil intacto. Um PATCH que carrega apenas campos, sem action, muda esses campos. Um PATCH que não carrega nada é um no-op que retorna a linha atual, não um erro.
Não existe DELETE em /v1/indirects, e CLOSED é terminal. A única saída do registro é close, que mantém a linha e a conta. Um indireto cadastrado por engano contra um tenant ativo permanece no registro desse tenant para sempre, e o ISPB dele fica retido contra a restrição de unicidade aberta até ser encerrado. Todo código de instituição que você usa é permanente — verifique name, ispb e o endpoint de entrega antes de chamar.

O que dá errado


Toda recusa que esse registro pode responder — seu status, sua condição exata, e o que foi ou não gravado quando ela disparou — está nas páginas de operação: Cadastrar um participante indireto, Atualizar um participante indireto, Consultar um participante indireto, Listar participantes indiretos, Consultar o JWK Set de um participante indireto, e Listar as transações de um participante indireto. O catálogo completo, com o detail exato que cada código carrega, é a Lista de erros do Pix JD.
Se deve repetir é decidido pelo status, não pela família do código. PIX-0107 e PIX-0123 são a mesma condição vista duas vezes: um 409 significa que o trilho leu a configuração e constatou que a chave está ausente — apenas um operador muda isso, então repetir entra em loop para sempre. Um 503 significa que a própria leitura não terminou, então a chave não é sabidamente ausente, e repetir é correto. Nenhum dos dois grava nada.

Para onde ir agora