Inyo

Testes no Sandbox

O sandbox permite conduzir um remetente pela verificação de identidade e uma transação por todo o seu ciclo de vida, sem documentos reais, revisores nem movimentação real de dinheiro. Quatro ferramentas tornam isso possível:

  • Números de documento reservados que decidem como termina a sessão KYC de um remetente.
  • Endpoints de resolução que resolvem uma sessão KYC retida para revisão, ou um envio de documento que ficou pendente — que em sandbox ninguém atenderia.
  • Valores do remetente sobre os quais o próprio sandbox da rede de payout age.
  • Um endpoint de forçar eventos que simula os callbacks externos (webhooks do gateway, notificações da rede de payout) que normalmente fazem uma transação avançar.

A página segue a ordem de uma integração: primeiro os remetentes, depois as transações.


Remetentes

Validação do CEP

A criação do remetente valida o CEP contra o seu país. Duas recusas são fáceis de provocar sem querer ao gerar dados de teste:

CEPPaísResultado
99999, 00000, AAAAAa maioria422 - valores de um único caractere repetido são recusados como dados de preenchimento
ABCUS422 - não corresponde ao formato postal do país
12345, 12345-6789USaceito

Se valores de preenchimento são permitidos ou não é configurado por país, então um valor recusado para um país pode ser aceito para outro.


Verificação de telefone

A verificação de identidade por telefone é simulada no sandbox e decidida pelo último dígito do número, de modo que você alcança cada ramo sem mudar nenhuma configuração. Veja Verificação de telefone -> Testes no sandbox para a tabela completa.


Verificar a identidade de um remetente

Todo o fluxo KYC funciona em sandbox, e você escolhe como cada sessão termina:

  1. Declare um documento no remetente, com um número reservado.
  2. O número decide o desfecho.
  3. Abra a sessão e leia o resultado em DocumentUpdatedEvents.
  4. Se ela ficar retida para revisão, resolva-a você mesmo.

Em sandbox, toda sessão que alcança um veredito fica retida para revisão — isso é deliberado, não um erro de configuração. Não há limiar de aprovação nem de recusa configurado, então um documento limpo e um defeituoso reportam igualmente PENDING_REVIEW, e o veredito é o que você resolve. Só os desfechos sem veredito — FAILED, EXPIRED e SUPERSEDED — são terminais por si só.

Declarar o documento

A criação do remetente verifica o formato de cada documento declarado contra o serviço KYC da Inyo e recusa um número que a verificação declara inválido, então um número inventado pode fazer a requisição falhar antes que algo seja persistido. Os números reservados abaixo são números reais e de formato válido, então passam: os de carteira de motorista e passaporte voltam válidos, e os de documento de identidade dos EUA voltam não avaliados, o que a criação do remetente também aceita.

Um número reservado é reconhecido pelo tipo de documento e pela jurisdição exatos. Declare-o com countryCode: "US", e uma carteira de motorista também com stateCode: "PA":

"documents": [
  { "type": "drivers_license", "document": "99900101", "countryCode": "US", "stateCode": "PA" }
]

A sessão verifica o primeiro documento elegível do remetente — carteira de motorista antes de passaporte, e este antes de documento de identidade — então o número reservado precisa estar nesse. Declarar um único documento é a forma mais simples de garantir isso.

Para testar a própria verificação de formato e não a sessão, use em vez disso os números reservados de verificação de formato. Eles devolvem um veredito escolhido, true, false ou null, e não ditam nenhuma sessão; false é como você chega à recusa da própria criação do remetente.

Forçando cada desfecho

O número do documento declarado decide como a sessão termina. Os números estão listados para drivers_license, id e passport, nessa ordem:

NúmeroO que a sessão fazO que DocumentUpdatedEvents entrega
clean — 99900101, 99900201, 999003001Conclui na criação com um resultado completoPENDING, e depois VERIFIED ou REJECTED quando você a resolve
capture_exhausted — 99900108, 99900208, 999003008Termina na criação como um remetente que esgotou as tentativas de capturaFAILED
link_expired — 99900113, 99900213, 999003013É criada já expirada; o link mostra a mensagem de link expiradoEXPIRED
Qualquer outro númeroAguarda uma captura real no widget, analisada como em produçãoO que a captura produzir; EXPIRED se o remetente não terminar em 49 horas

SUPERSEDED não precisa de número: abra uma segunda sessão para o mesmo remetente, ou declare de novo o documento dele com outro número, e a sessão anterior termina em SUPERSEDED. Os três desfechos sem veredito estão explicados em Quando uma sessão termina sem veredito.

Os demais cenários com veredito do catálogo — document_not_authentic, sanctions_hit e o resto — terminam como clean. Um cenário molda o resultado, não o veredito: ele decide qual verificação falha no resultado que Consultar uma Sessão de Verificação devolve, e o veredito continua sendo o que você resolve.

A criação da sessão responde 422 KYC_VALIDATION_ERROR, com o motivo em detail, quando um cenário não pode rodar no seu tenant — ele desvia numa verificação que a sua conta não tem habilitada, como sanctions_hit sem triagem de sanções, ou o remetente não tem nome, sobrenome ou data de nascimento, que um resultado ditado exige. Nada é criado.

Apenas sandbox. Os números reservados são habilitados por tenant. Se um deixar a sessão aguardando uma captura, peça ao seu contato na Inyo que habilite a simulação no seu tenant de sandbox.

Abrir a sessão

Abra-a com a mesma requisição de produção; nada nela muda. Uma sessão ditada por um número reservado é definitiva na criação: a resposta continua trazendo um widgetUrl, então a sua integração roda sem mudanças, mas não há nada a capturar por trás dele.

Leia o resultado em DocumentUpdatedEvents, cujo id é o id da sessão. Ele chega pouco depois da resposta, então o seu próprio registro da sessão já existe. Consultar uma Sessão de Verificação é a alternativa quando uma entrega se perde.

Resolver uma sessão KYC retida

Endpoint: POST /organizations/{tenant}/v2/senders/{personId}/kycSession/{sessionId}/resolve
Autenticação: Nível agente
Disponibilidade: Apenas sandbox/staging — este endpoint não existe em produção.

Ninguém revisa os seus remetentes de teste, então sem este endpoint uma sessão retida nunca alcança um estado terminal: nenhum nível de conformidade muda e o seu fluxo de DocumentUpdatedEvents para depois do PENDING.

Este endpoint a resolve. Ele executa o mesmo código que a decisão de um revisor da Inyo, então o veredito do documento, o nível de conformidade do remetente e o evento de saída são os reais — só a porta é diferente.

curl --request POST \
  --url https://{FQDN}/organizations/$TENANT/v2/senders/$PERSON_ID/kycSession/$SESSION_ID/resolve \
  --header 'Content-Type: application/json' \
  --header "x-api-key: $API_KEY" \
  --header "x-agent-id: $AGENT_ID" \
  --header "x-agent-api-key: $AGENT_KEY" \
  --data '{
  "outcome": "verified",
  "reason": "Liberada para o teste de integração"
}'
CampoNotas
outcomeverified ou rejected. Obrigatório.
reasonTexto livre, até 255 caracteres. Obrigatório — é armazenado junto com a resolução.

Responde 200 com a sessão no mesmo formato que Consultar uma Sessão de Verificação devolve, com o novo status e completedAt. Um assinante recebe um segundo DocumentUpdatedEvents com o mesmo id que o PENDING anterior carregava, de modo que os dois se correlacionam.

Qualquer sessão retida se qualifica, tenha sido ditada por um resultado de verificação simulado ou capturada de verdade no widget.

Recusas

Toda recusa carrega o status real da sessão junto de error e message, porque o código sozinho não diz o que fazer em seguida — NOT_HELD cobre tanto uma sessão ainda PENDING (volte mais tarde) quanto uma já decidida (pare e leia o resultado).

{ "error": "NOT_HELD", "status": "VERIFIED", "message": "This session is VERIFIED, not held for review." }
HTTPerrorSignificado
404NOT_FOUNDNão existe essa sessão para esta pessoa, neste tenant
409NOT_HELDA sessão não está retida para revisão — ainda pendente, ou já decidida
409MANAGED_TENANCYO seu tenancy é decidido pela plataforma de verificação; a retenção pertence à fila dela
409DECIDED_ELSEWHEREUma sessão posterior já decidiu este documento, então resolver esta não teria efeito
409ALREADY_RESOLVEDAlgo resolveu a sessão enquanto a sua requisição estava em curso
422VALIDATION_ERRORoutcome ausente ou fora dos dois valores, ou reason ausente ou longo demais

O endpoint não é idempotente: chamá-lo de novo após uma resolução bem-sucedida devolve 409 NOT_HELD com o status para o qual ficou, não 200. Ramifique pelo status em vez de tentar de novo às cegas.


Resolver uma revisão de documento pendente

Endpoint: POST /organizations/{tenant}/v2/senders/{personId}/documents/{documentId}/resolve
Autenticação: Nível agente
Disponibilidade: Apenas sandbox/staging — este endpoint não existe em produção.

Os envios ficam em PENDING no sandbox: o OCR com IA normalmente está desligado e ninguém atende a fila de revisão de um tenant de teste. O remetente nunca avança de nível e o fluxo de DocumentUpdatedEvents para depois do primeiro PENDING.

Isto resolve pela via real: a mesma linha de status de verificação, o mesmo webhook e o mesmo resultado de conformidade que a decisão de um revisor produz. verifiedBy carrega o prefixo sandbox:, de modo que uma decisão simulada continua distinguível.

Qualquer documento da pessoa serve, não apenas a comprovação de origem de fundos. Um documento de identidade enviado pelos endpoints de envio direto fica igualmente preso e é lido pelas mesmas verificações.

curl --request POST \
  --url https://{FQDN}/organizations/$TENANT/v2/senders/$PERSON_ID/documents/$DOCUMENT_ID/resolve \
  --header 'Content-Type: application/json' \
  --header "x-api-key: $API_KEY" \
  --header "x-agent-id: $AGENT_ID" \
  --header "x-agent-api-key: $AGENT_KEY" \
  --data '{
  "outcome": "verified",
  "reason": "Liberado para o passo a passo de integração"
}'

$DOCUMENT_ID é o id que a resposta do envio devolveu.

CampoNotas
outcomeverified ou rejected. Obrigatório.
reasonTexto livre, até 255 caracteres. Obrigatório — é armazenado com a resolução e mostrado ao revisor.

Responde 200 com o novo status do documento na mesma forma que Consultar o status de verificação atual devolve:

{
  "status": "verified",
  "reason": "Liberado para o passo a passo de integração",
  "verifiedBy": "sandbox:8f14e45f-ceea-467a-9f5e-9b2c4c9a1f77",
  "createdAt": "2026-09-29T12:10:37+00:00"
}

Um assinante recebe um DocumentUpdatedEvents com o id do documento e o novo verificationStatus.

Recusas

Toda recusa carrega o status real do documento junto a error e message, porque o código sozinho não diz o que fazer em seguida.

{ "error": "ALREADY_DECIDED", "status": "verified", "message": "This document is already verified; it is not waiting for a review." }
HTTPerrorSignificado
404NOT_FOUNDNão existe esse documento para esta pessoa, sob este tenant
409ALREADY_DECIDEDO documento já está verified ou rejected — leia o resultado em vez de resolvê-lo de novo
409NOT_PENDINGO documento está em outro estado que este endpoint não resolve
422VALIDATION_ERRORFalta outcome ou não é um dos dois valores, ou falta reason ou é longo demais

Este endpoint resolve um documento que está esperando; não reabre um já decidido. Não é idempotente — chamá-lo de novo após uma resolução bem-sucedida devolve 409 ALREADY_DECIDED com o status para o qual ficou, não 200. Ramifique pelo status em vez de tentar de novo às cegas.


Transações

Forçando Transições de Status

Endpoint: POST /organizations/{tenant}/fx/transactions/{transactionId}/events
Autenticação: Nível de agente
Disponibilidade: Somente sandbox/staging — este endpoint não existe em produção.

curl --request POST \
  --url https://{FQDN}/organizations/$TENANT/fx/transactions/$TRANSACTION_ID/events \
  --header 'Content-Type: application/json' \
  --header "x-api-key: $API_KEY" \
  --header "x-agent-id: $AGENT_ID" \
  --header "x-agent-api-key: $AGENT_KEY" \
  --data '{
  "event": "payment_authorized",
  "message": "Optional description"
}'

Eventos Disponíveis

EventoVálido a partir do estadoO que simula
payment_authorizedWaitingChallenge3dsO callback 3DS do gateway. Retoma o fluxo — despacha a integração de payout. Use quando a transação está presa aguardando o 3DS.
ach_settledWaitingSettlementO callback de liquidação ACH. Move a transação por PaymentSettled → PaymentCaptured e dispara a confirmação do payout.
payment_capturedReviewApprovedUma confirmação de captura de cartão. Dispara a confirmação do payout.
payout_paidPayoutAccepted, PayoutReleased, WaitingPayoutA rede de payout confirmando que o beneficiário recebeu os fundos. Move a transação para Paid → Completed.
payout_voidPayoutAccepted, PayoutHold, PayoutReleased, WaitingPayoutA rede de payout anulando a transação. Dependendo da configuração do tenant, reverte automaticamente ou fica parada em PendingReversalApproval.
hold_releasedPayoutHoldA rede de payout liberando uma retenção de conformidade. Retoma o fluxo.
cancelledQualquer estado cancelávelCancelamento iniciado pelo cliente (cancela o pagamento via void e cancela o payout).
refundedEstados reembolsáveisMarca a transação como reembolsada.

Se a transação não estiver em um estado válido para o evento, o endpoint retorna um erro explicando o estado atual.

Fluxo de Teste Típico (Cartão + 3DS)

  1. Crie a transação → status WaitingChallenge3ds, paymentStatus: ActionRequired
  2. Force o 3DS: { "event": "payment_authorized" }
  3. A transação progride automaticamente → PayoutAccepted → ManualReview → ReviewApproved → PaymentCaptured → WaitingPayout
  4. Force a entrega: { "event": "payout_paid" }
  5. A transação chega a Completed

Fluxo de Teste Típico (ACH)

  1. Crie a transação → progride para WaitingSettlement
  2. Force a liquidação: { "event": "ach_settled" }
  3. A transação progride automaticamente → PaymentSettled → PaymentCaptured → WaitingPayout
  4. Force a entrega: { "event": "payout_paid" }
  5. A transação chega a Completed

Cenários de teste da rede de payout

O sandbox envia os dados do seu remetente para a rede de payout exatamente como a produção faz, e o sandbox da própria rede decide o que acontece em seguida. Dois valores do remetente produzem um resultado deliberado. Todo o resto é aceito e pago.

Retenção de conformidade

O nome de remetente JULIO SOLANO provoca uma retenção de conformidade na rede de payout:

{ "firstName": "JULIO", "lastName": "SOLANO", "...": "..." }

A transação chega a PayoutHold após a integração do payout, com complianceStatus: Pending e payoutStatus: Processing. Libere-a com o evento forçado hold_released (ou aguarde a rede ou o backoffice liberarem).

Fluxo esperado:

... -> ProcessingPayout -> PayoutHold -> [retenção liberada] -> PayoutReleased -> ...

Remetente recusado pela rede

Um remetente cujo estado e CEP não combinam é recusado pela rede de payout antes de a transferência ser criada. 96738 é um CEP do Havaí, então enviá-lo com um endereço da Califórnia produz isto:

{ "address": { "stateCode": "CA", "city": "Los Angeles", "zipcode": "96738" } }

A transação não vira uma recusa que você possa tratar como tal. Ela termina em Failed / Failed e fica estacionada para intervenção manual, porque um problema de dados é tratado como recuperável e não como uma recusa da transferência:

complianceStatusFailed
payoutStatusFailed
MensagemPayout integration error. Please contact support if this persists.

A mensagem é genérica de propósito: o texto de erro da própria rede de payout não é exposto na API. Se você encontrar isso fora de um teste, a causa é quase sempre um endereço do remetente que a rede não consegue resolver.

Recusa do pagamento

Use um token de cartão que o sandbox do gateway recuse, ou um token expirado. A transação chega a PaymentDeclined -> Cancelled.

Recusa de 3DS

Quando for redirecionado para a página do desafio 3DS, escolha "Recusar" (onde o sandbox oferecer). O gateway reporta a falha e a transação passa para PaymentDeclined -> Cancelled.


Cartões de Teste

Use os cartões de teste do sandbox para simular cenários de 3DS. A lista completa está em Cartões de teste do Payments Gateway. Os mais comuns:

Número do CartãoBandeiraComportamento 3DS
4462030000000000VISAChallenge 3DS
4035874000424977VISAFrictionless (sem challenge)
5425230000004415MastercardChallenge 3DS
4000000000000002VISARecusado

Tokenize-os via SDK do gateway e depois passe o token como paymentMethod.token ao criar uma conta de financiamento.


Polling de Status

Depois de criar uma transação, faça polling em GET /organizations/{tenant}/fx/transactions/{transactionId} e observe:

CampoO que esperar
complianceStatusPending → Approved (após a integração de payout)
payoutStatusPending → Processing → Completed
paymentStatusActionRequired → null (após a conclusão do 3DS)
receiptDivulgações regulatórias (disponíveis desde a criação)

Para a trilha de auditoria completa das transições, use GET /fx/transactions/{transactionId}/status — veja Transação. Em produção, prefira webhooks em vez de polling.