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:
| CEP | País | Resultado |
|---|---|---|
99999, 00000, AAAAA | a maioria | 422 - valores de um único caractere repetido são recusados como dados de preenchimento |
ABC | US | 422 - não corresponde ao formato postal do país |
12345, 12345-6789 | US | aceito |
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:
- Declare um documento no remetente, com um número reservado.
- O número decide o desfecho.
- Abra a sessão e leia o resultado em
DocumentUpdatedEvents. - 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úmero | O que a sessão faz | O que DocumentUpdatedEvents entrega |
|---|---|---|
clean — 99900101, 99900201, 999003001 | Conclui na criação com um resultado completo | PENDING, e depois VERIFIED ou REJECTED quando você a resolve |
capture_exhausted — 99900108, 99900208, 999003008 | Termina na criação como um remetente que esgotou as tentativas de captura | FAILED |
link_expired — 99900113, 99900213, 999003013 | É criada já expirada; o link mostra a mensagem de link expirado | EXPIRED |
| Qualquer outro número | Aguarda uma captura real no widget, analisada como em produção | O 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"
}'
| Campo | Notas |
|---|---|
outcome | verified ou rejected. Obrigatório. |
reason | Texto 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." }
| HTTP | error | Significado |
|---|---|---|
404 | NOT_FOUND | Não existe essa sessão para esta pessoa, neste tenant |
409 | NOT_HELD | A sessão não está retida para revisão — ainda pendente, ou já decidida |
409 | MANAGED_TENANCY | O seu tenancy é decidido pela plataforma de verificação; a retenção pertence à fila dela |
409 | DECIDED_ELSEWHERE | Uma sessão posterior já decidiu este documento, então resolver esta não teria efeito |
409 | ALREADY_RESOLVED | Algo resolveu a sessão enquanto a sua requisição estava em curso |
422 | VALIDATION_ERROR | outcome 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.
| Campo | Notas |
|---|---|
outcome | verified ou rejected. Obrigatório. |
reason | Texto 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." }
| HTTP | error | Significado |
|---|---|---|
404 | NOT_FOUND | Não existe esse documento para esta pessoa, sob este tenant |
409 | ALREADY_DECIDED | O documento já está verified ou rejected — leia o resultado em vez de resolvê-lo de novo |
409 | NOT_PENDING | O documento está em outro estado que este endpoint não resolve |
422 | VALIDATION_ERROR | Falta 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
| Evento | Válido a partir do estado | O que simula |
|---|---|---|
payment_authorized | WaitingChallenge3ds | O callback 3DS do gateway. Retoma o fluxo — despacha a integração de payout. Use quando a transação está presa aguardando o 3DS. |
ach_settled | WaitingSettlement | O callback de liquidação ACH. Move a transação por PaymentSettled → PaymentCaptured e dispara a confirmação do payout. |
payment_captured | ReviewApproved | Uma confirmação de captura de cartão. Dispara a confirmação do payout. |
payout_paid | PayoutAccepted, PayoutReleased, WaitingPayout | A rede de payout confirmando que o beneficiário recebeu os fundos. Move a transação para Paid → Completed. |
payout_void | PayoutAccepted, PayoutHold, PayoutReleased, WaitingPayout | A rede de payout anulando a transação. Dependendo da configuração do tenant, reverte automaticamente ou fica parada em PendingReversalApproval. |
hold_released | PayoutHold | A rede de payout liberando uma retenção de conformidade. Retoma o fluxo. |
cancelled | Qualquer estado cancelável | Cancelamento iniciado pelo cliente (cancela o pagamento via void e cancela o payout). |
refunded | Estados reembolsáveis | Marca 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)
- Crie a transação → status
WaitingChallenge3ds,paymentStatus: ActionRequired - Force o 3DS:
{ "event": "payment_authorized" } - A transação progride automaticamente →
PayoutAccepted→ManualReview→ReviewApproved→PaymentCaptured→WaitingPayout - Force a entrega:
{ "event": "payout_paid" } - A transação chega a
Completed
Fluxo de Teste Típico (ACH)
- Crie a transação → progride para
WaitingSettlement - Force a liquidação:
{ "event": "ach_settled" } - A transação progride automaticamente →
PaymentSettled→PaymentCaptured→WaitingPayout - Force a entrega:
{ "event": "payout_paid" } - 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:
complianceStatus | Failed |
payoutStatus | Failed |
| Mensagem | Payout 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ão | Bandeira | Comportamento 3DS |
|---|---|---|
4462030000000000 | VISA | Challenge 3DS |
4035874000424977 | VISA | Frictionless (sem challenge) |
5425230000004415 | Mastercard | Challenge 3DS |
4000000000000002 | VISA | Recusado |
Tokenize-os via SDK do gateway e depois passe o token como
paymentMethod.tokenao 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:
| Campo | O que esperar |
|---|---|
complianceStatus | Pending → Approved (após a integração de payout) |
payoutStatus | Pending → Processing → Completed |
paymentStatus | ActionRequired → null (após a conclusão do 3DS) |
receipt | Divulgaçõ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.
