Fluxo completo de autenticação biométrica em conformidade com a Nota Técnica 65/2023 (INSS/Dataprev) para contratos de empréstimo consignado.
| Item | Valor |
|---|---|
| Base URL (HML) | https://api-hml.signdocs.com.br |
| Autenticação | OAuth2 client_credentials — Bearer JWT |
| Content-Type | application/json |
| Formato de erros | RFC 7807 application/problem+json |
A NT65/2023 estabelece 15 requisitos técnicos para autenticação biométrica em consignados INSS:
A API implementa um único perfil consolidado para conformidade NT65:
| Perfil | Descrição |
|---|---|
BIOMETRIC_SERPRO_AUTO_FALLBACK |
Verificação contra base SERPRO com fallback automático por documento. Se o SERPRO não possui imagem biométrica do beneficiário, a etapa DOCUMENT_PHOTO_MATCH é ativada automaticamente — sem necessidade de criar uma segunda transação. |
Nota: o perfilBIOMETRIC_SERPRO_AUTO_FALLBACKsubstitui a abordagem anterior de dois perfis separados (BIOMETRIC_SERPRO+BIOMETRIC_DOCUMENT_FALLBACK). Todo o fluxo é resolvido em uma única transação.
nt65ComplianceEnabled: trueserpro.enabled: true — integração com SERPRO DataValiddocumentExtractionEnabled: true — extração facial de documentos (necessário para fallback)cpf (11 dígitos) e birthDate (YYYY-MM-DD)transactions:write steps:write evidence:readfallbackDocument) na criação da transação. Se o fallback for acionado, a etapa DOCUMENT_PHOTO_MATCH utilizará essa imagem automaticamente, sem necessidade de nova captura.export BASE_URL="https://api-hml.signdocs.com.br"
export TOKEN="seu_jwt_token"
O perfil BIOMETRIC_SERPRO_AUTO_FALLBACK exige cpf e birthDate no signer. A API injeta automaticamente a etapa PURPOSE_DISCLOSURE e cria uma etapa DOCUMENT_PHOTO_MATCH em status DORMANT (dormante — ativada automaticamente se necessário).
Opcionalmente, inclua fallbackDocument com a foto do documento de identidade. Se o fallback for acionado, essa imagem será usada automaticamente.
TX=$(curl -s -X POST "$BASE_URL/v1/transactions" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: nt65-$(date +%s)" \
-d '{
"purpose": "DOCUMENT_SIGNATURE",
"policy": { "profile": "BIOMETRIC_SERPRO_AUTO_FALLBACK" },
"signer": {
"name": "Maria Santos",
"userExternalId": "user-001",
"cpf": "12345678900",
"birthDate": "1985-03-20"
},
"document": {
"content": "'$(base64 -w0 consignado.pdf)'",
"filename": "consignado.pdf"
},
"fallbackDocument": {
"image": "'$(base64 -w0 cnh_foto.jpg)'",
"type": "CNH"
},
"metadata": { "loanType": "CONSIGNADO_INSS" }
}')
TX_ID=$(echo "$TX" | jq -r '.transactionId')
echo "Transação criada: $TX_ID"
const txRes = await fetch(`${BASE_URL}/v1/transactions`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
"X-Idempotency-Key": `nt65-${Date.now()}`,
},
body: JSON.stringify({
purpose: "DOCUMENT_SIGNATURE",
policy: { profile: "BIOMETRIC_SERPRO_AUTO_FALLBACK" },
signer: {
name: "Maria Santos",
userExternalId: "user-001",
cpf: "12345678900",
birthDate: "1985-03-20",
},
document: { content: pdfBase64, filename: "consignado.pdf" },
fallbackDocument: {
image: cnhPhotoBase64,
type: "CNH",
},
metadata: { loanType: "CONSIGNADO_INSS" },
}),
});
const tx = await txRes.json();
import requests, base64
tx = requests.post(f"{BASE_URL}/v1/transactions", headers={
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json",
"X-Idempotency-Key": f"nt65-{int(time.time())}",
}, json={
"purpose": "DOCUMENT_SIGNATURE",
"policy": {"profile": "BIOMETRIC_SERPRO_AUTO_FALLBACK"},
"signer": {
"name": "Maria Santos",
"userExternalId": "user-001",
"cpf": "12345678900",
"birthDate": "1985-03-20",
},
"document": {"content": pdf_base64, "filename": "consignado.pdf"},
"fallbackDocument": {
"image": cnh_photo_base64,
"type": "CNH",
},
"metadata": {"loanType": "CONSIGNADO_INSS"},
}).json()
tx_id = tx["transactionId"]
Nota: o campofallbackDocumenté opcional. Se omitido e o fallback for acionado, você deverá enviar a foto do documento manualmente ao completar a etapaDOCUMENT_PHOTO_MATCH.
O perfil gera 5 etapas. A etapa DOCUMENT_PHOTO_MATCH inicia em status DORMANT — ela só será ativada se o SERPRO não possuir imagem biométrica do beneficiário.
STEPS=$(curl -s "$BASE_URL/v1/transactions/$TX_ID/steps" \
-H "Authorization: Bearer $TOKEN")
echo "$STEPS" | jq '.steps[] | {type, status}'
# { "type": "PURPOSE_DISCLOSURE", "status": "PENDING" }
# { "type": "BIOMETRIC_LIVENESS", "status": "PENDING" }
# { "type": "BIOMETRIC_MATCH", "status": "PENDING" }
# { "type": "SERPRO_IDENTITY_CHECK", "status": "PENDING" }
# { "type": "DOCUMENT_PHOTO_MATCH", "status": "DORMANT" } <- ativada automaticamente se necessário
const stepsRes = await fetch(`${BASE_URL}/v1/transactions/${tx.transactionId}/steps`, {
headers: { Authorization: `Bearer ${TOKEN}` },
});
const { steps } = await stepsRes.json();
// steps[0].type === "PURPOSE_DISCLOSURE" -> PENDING
// steps[1].type === "BIOMETRIC_LIVENESS" -> PENDING
// steps[2].type === "BIOMETRIC_MATCH" -> PENDING
// steps[3].type === "SERPRO_IDENTITY_CHECK" -> PENDING
// steps[4].type === "DOCUMENT_PHOTO_MATCH" -> DORMANT
// Se fallbackDocument foi pré-enviado:
const dormantStep = steps.find((s) => s.type === "DOCUMENT_PHOTO_MATCH");
console.log(dormantStep.fallbackDocumentPreUploaded); // true
steps_res = requests.get(
f"{BASE_URL}/v1/transactions/{tx_id}/steps",
headers={"Authorization": f"Bearer {TOKEN}"},
).json()
steps = steps_res["steps"]
# steps[0]["type"] == "PURPOSE_DISCLOSURE" -> "PENDING"
# steps[1]["type"] == "BIOMETRIC_LIVENESS" -> "PENDING"
# steps[2]["type"] == "BIOMETRIC_MATCH" -> "PENDING"
# steps[3]["type"] == "SERPRO_IDENTITY_CHECK" -> "PENDING"
# steps[4]["type"] == "DOCUMENT_PHOTO_MATCH" -> "DORMANT"
dormant = next(s for s in steps if s["type"] == "DOCUMENT_PHOTO_MATCH")
print(dormant["fallbackDocumentPreUploaded"]) # True (se pré-enviado)
Importante: Etapas com statusDORMANTnão podem ser iniciadas manualmente — a API retorna409 Conflictse você tentar. A ativação ocorre automaticamente quando o SERPRO indica ausência de biometria.
Inicie a etapa (a API envia a notificação ao beneficiário) e complete com acknowledged: true.
DISCLOSURE_ID=$(echo "$STEPS" | jq -r '.steps[] | select(.type=="PURPOSE_DISCLOSURE") | .stepId')
# Iniciar — envia notificação ao beneficiário
curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$DISCLOSURE_ID/start" \
-H "Authorization: Bearer $TOKEN"
# Completar — beneficiário reconheceu
curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$DISCLOSURE_ID/complete" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "acknowledged": true }'
const disclosureStep = steps.find((s) => s.type === "PURPOSE_DISCLOSURE");
await fetch(`${BASE_URL}/v1/transactions/${tx.transactionId}/steps/${disclosureStep.stepId}/start`, {
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}` },
});
const disclosure = await fetch(
`${BASE_URL}/v1/transactions/${tx.transactionId}/steps/${disclosureStep.stepId}/complete`,
{
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({ acknowledged: true }),
}
).then((r) => r.json());
disclosure_step = next(s for s in steps if s["type"] == "PURPOSE_DISCLOSURE")
requests.post(
f"{BASE_URL}/v1/transactions/{tx_id}/steps/{disclosure_step['stepId']}/start",
headers={"Authorization": f"Bearer {TOKEN}"},
)
disclosure = requests.post(
f"{BASE_URL}/v1/transactions/{tx_id}/steps/{disclosure_step['stepId']}/complete",
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
json={"acknowledged": True},
).json()
A NT65 exige geolocalização em todas as etapas biométricas.
LIVENESS_ID=$(echo "$STEPS" | jq -r '.steps[] | select(.type=="BIOMETRIC_LIVENESS") | .stepId')
# Iniciar
SESSION=$(curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$LIVENESS_ID/start" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "captureMode": "HOSTED_PAGE" }')
SESSION_ID=$(echo "$SESSION" | jq -r '.livenessSessionId')
HOSTED_URL=$(echo "$SESSION" | jq -r '.hostedUrl')
echo "Redirecionar beneficiário para: $HOSTED_URL"
# Completar (após captura facial)
curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$LIVENESS_ID/complete" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"livenessSessionId": "'$SESSION_ID'",
"geolocation": {
"latitude": -23.5505,
"longitude": -46.6333,
"accuracy": 10.5,
"source": "GPS"
}
}'
const livenessStep = steps.find((s) => s.type === "BIOMETRIC_LIVENESS");
const session = await fetch(
`${BASE_URL}/v1/transactions/${tx.transactionId}/steps/${livenessStep.stepId}/start`,
{
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({ captureMode: "HOSTED_PAGE" }),
}
).then((r) => r.json());
// Após captura facial na hostedUrl...
const liveness = await fetch(
`${BASE_URL}/v1/transactions/${tx.transactionId}/steps/${livenessStep.stepId}/complete`,
{
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({
livenessSessionId: session.livenessSessionId,
geolocation: { latitude: -23.5505, longitude: -46.6333, accuracy: 10.5, source: "GPS" },
}),
}
).then((r) => r.json());
liveness_step = next(s for s in steps if s["type"] == "BIOMETRIC_LIVENESS")
session = requests.post(
f"{BASE_URL}/v1/transactions/{tx_id}/steps/{liveness_step['stepId']}/start",
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
json={"captureMode": "HOSTED_PAGE"},
).json()
# Após captura facial na hosted_url...
liveness = requests.post(
f"{BASE_URL}/v1/transactions/{tx_id}/steps/{liveness_step['stepId']}/complete",
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
json={
"livenessSessionId": session["livenessSessionId"],
"geolocation": {"latitude": -23.5505, "longitude": -46.6333, "accuracy": 10.5, "source": "GPS"},
},
).json()
MATCH_ID=$(echo "$STEPS" | jq -r '.steps[] | select(.type=="BIOMETRIC_MATCH") | .stepId')
curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$MATCH_ID/start" \
-H "Authorization: Bearer $TOKEN"
curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$MATCH_ID/complete" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"geolocation": {
"latitude": -23.5505,
"longitude": -46.6333,
"accuracy": 10.5,
"source": "GPS"
}
}'
const matchStep = steps.find((s) => s.type === "BIOMETRIC_MATCH");
await fetch(
`${BASE_URL}/v1/transactions/${tx.transactionId}/steps/${matchStep.stepId}/start`,
{ method: "POST", headers: { Authorization: `Bearer ${TOKEN}` } }
);
const match = await fetch(
`${BASE_URL}/v1/transactions/${tx.transactionId}/steps/${matchStep.stepId}/complete`,
{
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({
geolocation: { latitude: -23.5505, longitude: -46.6333, accuracy: 10.5, source: "GPS" },
}),
}
).then((r) => r.json());
match_step = next(s for s in steps if s["type"] == "BIOMETRIC_MATCH")
requests.post(
f"{BASE_URL}/v1/transactions/{tx_id}/steps/{match_step['stepId']}/start",
headers={"Authorization": f"Bearer {TOKEN}"},
)
match = requests.post(
f"{BASE_URL}/v1/transactions/{tx_id}/steps/{match_step['stepId']}/complete",
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
json={
"geolocation": {"latitude": -23.5505, "longitude": -46.6333, "accuracy": 10.5, "source": "GPS"},
},
).json()
A verificação SERPRO é processada automaticamente (server-side). Basta iniciar e completar. A resposta indica um de três desfechos:
| Desfecho | status |
fallback |
Próximo passo |
|---|---|---|---|
| Sucesso — SERPRO validou biometria | COMPLETED |
ausente | Finalizar transação (etapa DORMANT é ignorada) |
| Biometria indisponível — SERPRO não possui imagem | SKIPPED |
{ triggered: true, nextStepId, ... } |
Completar DOCUMENT_PHOTO_MATCH (ativada automaticamente) |
| Falha — dados incorretos ou score baixo | STARTED ou FAILED |
ausente | Corrigir dados e retentar, ou abortar |
SERPRO_ID=$(echo "$STEPS" | jq -r '.steps[] | select(.type=="SERPRO_IDENTITY_CHECK") | .stepId')
curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$SERPRO_ID/start" \
-H "Authorization: Bearer $TOKEN"
SERPRO=$(curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$SERPRO_ID/complete" \
-H "Authorization: Bearer $TOKEN")
echo "$SERPRO" | jq '{status, fallback, result}'
Resposta — Sucesso (SERPRO validou):
{
"status": "COMPLETED",
"result": {
"serproIdentity": {
"valid": true,
"biometricMatch": true,
"biometricConfidence": 0.98,
"nameMatch": true,
"birthDateMatch": true
}
}
}
Resposta — Biometria indisponível (fallback acionado):
{
"status": "SKIPPED",
"fallback": {
"triggered": true,
"nextStepType": "DOCUMENT_PHOTO_MATCH",
"nextStepId": "step_abc123",
"fallbackDocumentPreUploaded": true
}
}
Quando fallback.triggered é true, a etapa DOCUMENT_PHOTO_MATCH muda de DORMANT para PENDING automaticamente. Prossiga para a seção 3.7.
const serproStep = steps.find((s) => s.type === "SERPRO_IDENTITY_CHECK");
await fetch(
`${BASE_URL}/v1/transactions/${tx.transactionId}/steps/${serproStep.stepId}/start`,
{ method: "POST", headers: { Authorization: `Bearer ${TOKEN}` } }
);
const serproRes = await fetch(
`${BASE_URL}/v1/transactions/${tx.transactionId}/steps/${serproStep.stepId}/complete`,
{ method: "POST", headers: { Authorization: `Bearer ${TOKEN}` } }
).then((r) => r.json());
if (serproRes.status === "COMPLETED") {
// Sucesso — finalizar transação diretamente
console.log("SERPRO validou:", serproRes.result.serproIdentity.biometricConfidence);
} else if (serproRes.status === "SKIPPED" && serproRes.fallback?.triggered) {
// Fallback acionado — completar DOCUMENT_PHOTO_MATCH
const fallbackStepId = serproRes.fallback.nextStepId;
console.log("Fallback acionado, completar etapa:", fallbackStepId);
} else {
// Falha — dados incorretos, retentar ou abortar
console.error("SERPRO falhou:", serproRes.status);
}
serpro_step = next(s for s in steps if s["type"] == "SERPRO_IDENTITY_CHECK")
requests.post(
f"{BASE_URL}/v1/transactions/{tx_id}/steps/{serpro_step['stepId']}/start",
headers={"Authorization": f"Bearer {TOKEN}"},
)
serpro = requests.post(
f"{BASE_URL}/v1/transactions/{tx_id}/steps/{serpro_step['stepId']}/complete",
headers={"Authorization": f"Bearer {TOKEN}"},
).json()
if serpro["status"] == "COMPLETED":
# Sucesso — finalizar transação
print("SERPRO validou:", serpro["result"]["serproIdentity"]["biometricConfidence"])
elif serpro["status"] == "SKIPPED" and serpro.get("fallback", {}).get("triggered"):
# Fallback acionado — completar DOCUMENT_PHOTO_MATCH
fallback_step_id = serpro["fallback"]["nextStepId"]
print(f"Fallback acionado, completar etapa: {fallback_step_id}")
else:
# Falha — dados incorretos
print(f"SERPRO falhou: {serpro['status']}")
Esta etapa só é relevante quando o SERPRO retorna SKIPPED com fallback.triggered: true. Nesse caso, a etapa DOCUMENT_PHOTO_MATCH muda de DORMANT para PENDING automaticamente.
Se você pré-enviou fallbackDocument na criação da transação: basta indicar o documentType ao completar — a imagem pré-enviada será utilizada.
Se não pré-enviou: envie documentImage (base64) junto com documentType.
FALLBACK_STEP_ID=$(echo "$SERPRO" | jq -r '.fallback.nextStepId')
curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$FALLBACK_STEP_ID/start" \
-H "Authorization: Bearer $TOKEN"
# Com fallbackDocument pré-enviado, basta informar documentType
DOC_MATCH=$(curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$FALLBACK_STEP_ID/complete" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"documentType": "CNH",
"geolocation": {
"latitude": -23.5505,
"longitude": -46.6333,
"accuracy": 10.5,
"source": "GPS"
}
}')
echo "$DOC_MATCH" | jq '.result.documentPhotoMatch'
# { "similarity": 97.5, "documentType": "CNH", "biographicValidation": { "overallValid": true } }
DOC_MATCH=$(curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/steps/$FALLBACK_STEP_ID/complete" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"documentImage": "'$(base64 -w0 cnh_foto.jpg)'",
"documentType": "CNH",
"geolocation": {
"latitude": -23.5505,
"longitude": -46.6333,
"accuracy": 10.5,
"source": "GPS"
}
}')
const fallbackStepId = serproRes.fallback.nextStepId;
await fetch(
`${BASE_URL}/v1/transactions/${tx.transactionId}/steps/${fallbackStepId}/start`,
{ method: "POST", headers: { Authorization: `Bearer ${TOKEN}` } }
);
// Com fallbackDocument pré-enviado:
const docMatch = await fetch(
`${BASE_URL}/v1/transactions/${tx.transactionId}/steps/${fallbackStepId}/complete`,
{
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({
documentType: "CNH",
geolocation: { latitude: -23.5505, longitude: -46.6333, accuracy: 10.5, source: "GPS" },
}),
}
).then((r) => r.json());
// Sem pré-envio — incluir documentImage:
// body: JSON.stringify({
// documentImage: documentPhotoBase64,
// documentType: "CNH",
// geolocation: { ... },
// })
fallback_step_id = serpro["fallback"]["nextStepId"]
requests.post(
f"{BASE_URL}/v1/transactions/{tx_id}/steps/{fallback_step_id}/start",
headers={"Authorization": f"Bearer {TOKEN}"},
)
# Com fallbackDocument pré-enviado:
doc_match = requests.post(
f"{BASE_URL}/v1/transactions/{tx_id}/steps/{fallback_step_id}/complete",
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
json={
"documentType": "CNH",
"geolocation": {"latitude": -23.5505, "longitude": -46.6333, "accuracy": 10.5, "source": "GPS"},
},
).json()
# Sem pré-envio — incluir documentImage:
# json={
# "documentImage": document_photo_base64,
# "documentType": "CNH",
# "geolocation": {...},
# }
Após completar todas as etapas ativas, finalize a transação. Etapas com status DORMANT são excluídas automaticamente da evidência (não bloqueiam a finalização).
A resposta inclui submissionDeadline (prazo de 7 dias úteis para submissão ao INSS).
FINALIZED=$(curl -s -X POST "$BASE_URL/v1/transactions/$TX_ID/finalize" \
-H "Authorization: Bearer $TOKEN")
echo "$FINALIZED" | jq '{status, submissionDeadline, deadlineStatus, evidenceId}'
# { "status": "COMPLETED", "submissionDeadline": "2026-03-12T23:59:59Z", "deadlineStatus": "PENDING", "evidenceId": "ev_..." }
const finalized = await fetch(`${BASE_URL}/v1/transactions/${tx.transactionId}/finalize`, {
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}` },
}).then((r) => r.json());
console.log(finalized.status); // "COMPLETED"
console.log(finalized.submissionDeadline); // "2026-03-12T23:59:59Z"
console.log(finalized.deadlineStatus); // "PENDING"
console.log(finalized.evidenceId); // "ev_..."
finalized = requests.post(
f"{BASE_URL}/v1/transactions/{tx_id}/finalize",
headers={"Authorization": f"Bearer {TOKEN}"},
).json()
print(finalized["status"]) # "COMPLETED"
print(finalized["submissionDeadline"]) # "2026-03-12T23:59:59Z"
print(finalized["deadlineStatus"]) # "PENDING"
print(finalized["evidenceId"]) # "ev_..."
A NT65 exige geolocalização em todas as etapas biométricas. O objeto geolocation aceita:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
latitude |
number | Sim | -90 a 90 |
longitude |
number | Sim | -180 a 180 |
accuracy |
number | Não | Precisão em metros |
source |
string | Não | GPS, IP, WIFI ou CELL |
Se a geolocalização não for enviada, a API retorna:
{
"type": "https://api.signdocs.com.br/problems/validation-error",
"title": "Erro de validação",
"status": 422,
"detail": "Geolocalização obrigatória para etapas biométricas NT65",
"code": "GEOLOCATION_REQUIRED"
}
Após a finalização, a transação recebe um submissionDeadline (7 dias úteis para submissão ao INSS). O campo deadlineStatus pode ser:
| Status | Descrição |
|---|---|
PENDING |
Dentro do prazo |
APPROACHING |
Faltam 2 dias úteis ou menos |
OVERDUE |
Prazo expirado |
Consulte o status a qualquer momento:
curl -s "$BASE_URL/v1/transactions/$TX_ID" \
-H "Authorization: Bearer $TOKEN" | jq '{submissionDeadline, deadlineStatus}'
Além dos webhooks padrão, o fluxo NT65 emite eventos adicionais:
| Evento | Quando | Payload extra |
|---|---|---|
STEP.PURPOSE_DISCLOSURE_SENT |
Notificação de divulgação enviada | notificationChannel, disclosureVersion |
TRANSACTION.DEADLINE_APPROACHING |
Faltam 2 dias úteis | submissionDeadline, deadlineStatus |
TRANSACTION.FALLBACK |
Fallback acionado automaticamente (SERPRO sem biometria) | fallbackReason, fallbackStepId, fallbackDocumentPreUploaded |
O evento TRANSACTION.FALLBACK é emitido quando a etapa SERPRO_IDENTITY_CHECK retorna SKIPPED e a etapa DOCUMENT_PHOTO_MATCH é ativada. Use este webhook para notificar sua aplicação de que o fluxo alternativo foi acionado.
Registre webhooks para esses eventos:
curl -s -X POST "$BASE_URL/v1/webhooks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://seu-servidor.com/webhooks/nt65",
"events": [
"STEP.PURPOSE_DISCLOSURE_SENT",
"TRANSACTION.DEADLINE_APPROACHING",
"TRANSACTION.FALLBACK",
"TRANSACTION.COMPLETED"
]
}'
| Campo | Obrigatório | Condicional |
|---|---|---|
signer.cpf |
Sim | — |
signer.birthDate |
Sim | — |
signer.name |
Sim | — |
geolocation (liveness) |
Sim | — |
geolocation (match) |
Sim | — |
geolocation (doc match) |
— | Sim (quando fallback acionado) |
documentImage |
— | Sim (quando fallback acionado e fallbackDocument não foi pré-enviado) |
documentType |
— | Sim (quando fallback acionado) |
fallbackDocument.image |
— | Opcional (na criação da transação) |
fallbackDocument.type |
— | Obrigatório se fallbackDocument.image for enviado |
| Código HTTP | Código de erro | Causa | Solução |
|---|---|---|---|
| 400 | INVALID_FALLBACK_DOCUMENT |
fallbackDocument enviado em perfil que não é BIOMETRIC_SERPRO_AUTO_FALLBACK |
Usar o perfil correto ou remover o campo |
| 409 | STEP_IS_DORMANT |
Tentativa de iniciar uma etapa dormante manualmente | Não iniciar DOCUMENT_PHOTO_MATCH diretamente — ela é ativada automaticamente quando o SERPRO retorna SKIPPED |
| 422 | GEOLOCATION_REQUIRED |
Geolocalização ausente em etapa biométrica | Incluir objeto geolocation |
| 422 | CPF_REQUIRED |
CPF ausente no signer para perfil NT65 | Informar cpf no signer |
| 422 | BIRTH_DATE_REQUIRED |
Data de nascimento ausente para perfil NT65 | Informar birthDate no signer |
| 422 | PURPOSE_NOT_ACKNOWLEDGED |
Tentativa de prosseguir sem completar PURPOSE_DISCLOSURE | Completar etapa com acknowledged: true |
| 422 | DOCUMENT_FACE_NOT_FOUND |
Face não detectada na foto do documento | Enviar foto com melhor qualidade |
| 422 | DEADLINE_OVERDUE |
Prazo de submissão expirado | Criar nova transação |
| 502 | SERPRO_UNAVAILABLE |
SERPRO DataValid temporariamente indisponível | Retry com backoff exponencial |
Criar transação (BIOMETRIC_SERPRO_AUTO_FALLBACK)
|
+- PURPOSE_DISCLOSURE --- acknowledged: true
+- BIOMETRIC_LIVENESS --- prova de vida + geolocalização
+- BIOMETRIC_MATCH ------ comparação facial + geolocalização
|
+- SERPRO_IDENTITY_CHECK
|
+- status: COMPLETED (valid: true)
| -> Finalizar transação normalmente
| -> Etapa DORMANT (DOCUMENT_PHOTO_MATCH) é ignorada na evidência
|
+- status: SKIPPED (fallback.triggered: true)
| -> DOCUMENT_PHOTO_MATCH muda de DORMANT -> PENDING
| -> Iniciar e completar DOCUMENT_PHOTO_MATCH
| +- Com fallbackDocument pré-enviado: enviar apenas documentType
| +- Sem pré-envio: enviar documentImage + documentType
| -> Finalizar transação (evidência inclui fallbackUsed: true)
|
+- status: STARTED ou FAILED (dados incorretos / score baixo)
-> Fallback NÃO é acionado
-> Corrigir dados e retentar, ou abortar transação
Antes de ir para produção com fluxos NT65, verifique:
nt65ComplianceEnabled ativa no tenantserpro.enabled ativa no tenantdocumentExtractionEnabled ativa no tenantcpf e birthDate enviados em todas as transações NT65BIOMETRIC_SERPRO_AUTO_FALLBACK utilizado (perfil único consolidado)PURPOSE_DISCLOSURE completada antes das etapas biométricasgeolocation enviada em BIOMETRIC_LIVENESS, BIOMETRIC_MATCH e DOCUMENT_PHOTO_MATCHTRANSACTION.FALLBACK registrado para ser notificado quando o fallback é acionadoTRANSACTION.DEADLINE_APPROACHING registrado para monitorar prazossubmissionDeadline monitorado e submissão ao INSS dentro do prazofallbackDocument pré-enviado na criação da transação (recomendado para melhor UX)