Collect (Servidor para Servidor)
Dois caminhos de verificação
O Guardian SDK oferece duas formas de verificar dispositivos. Escolha com base na sua arquitetura:
verify() | collect() | |
|---|---|---|
| Quem chama a Surt | O SDK (a partir do dispositivo) | Seu backend (servidor para servidor) |
| Requisito de JWT | Obrigatório, novo a cada chamada | Opcional (apenas para o IP do dispositivo) |
| Chamadas de rede do SDK | Sim | Apenas GET /geolocation/client-ip quando um JWT é fornecido |
| Seu backend envolvido | Não | Sim |
| Resposta | VerificationResult (permitido/negado) | CollectResult (payload criptografado) |
| Ideal para | Integração simples, decisões no frontend | Decisões no backend, lógica personalizada, requisitos de auditoria |
Fluxo de verify()
App → obter JWT novo do seu backend → SDK.verify(jwt) → Surt backend → risk decision → App
O SDK chama a Surt diretamente e retorna allowed: true/false. Seu aplicativo age sobre a decisão imediatamente. Um JWT novo é obrigatório a cada chamada.
Fluxo de collect()
App → SDK.collect() → encrypted payload → App → Your backend → Surt /evaluate → Your backend → App
O SDK coleta dados do dispositivo e os criptografa localmente. Sem argumentos, collect() faz zero chamadas de rede para a Surt. Seu backend envia o payload para o endpoint de avaliação da Surt, recebe a avaliação de risco completa e decide o que retornar ao seu aplicativo.
Quando usar collect()
- Seu backend precisa dos dados de risco antes de responder ao cliente
- Você quer combinar o risco do dispositivo com sua própria lógica de negócio no lado do servidor
- Você precisa de controle total sobre o que o cliente vê
- Conformidade exige que todas as chamadas a terceiros se originem da sua infraestrutura
Uso
1. Coletar no dispositivo
import { useGuardian } from '@surtai/guardian-rn';
function PaymentScreen() {
const { collect } = useGuardian();
const handlePayment = async () => {
// O JWT é opcional para collect(). Chame collect() sem argumentos,
// ou passe collect(jwt) apenas se quiser que o IP público do
// dispositivo seja resolvido no payload.
const { payload } = await collect();
// Envie o payload para o SEU backend
const response = await fetch('https://your-api.com/verify-device', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
payload,
amount: 500,
currency: 'USD',
}),
});
const result = await response.json();
// Seu backend já tomou a decisão de risco
};
}
Para collect(), passe um JWT para collect(jwt) apenas se quiser que o IP público do dispositivo seja resolvido no payload; caso contrário, chame collect() sem argumentos. Diferente de verify(), um JWT de collect() pode ser reutilizado ou omitido - collect() não exige um JWT por chamada.
2. Chamar a Surt a partir do seu backend
Seu backend recebe o payload criptografado do aplicativo e então chama o endpoint de avaliação da Surt:
POST https://api.surt.com/geolocation/transactions/evaluate
Content-Type: application/json
Authorization: Bearer YOUR_SURT_API_KEY
{
"customer_id": "user_123",
"transaction_type": "withdrawal",
"transaction_name": "User Payment",
"payload": {
"type": "encrypted",
"data": "<payload from SDK>"
},
"config": {
"response": {
"address": { "type": "include" }
}
}
}
Campos da requisição
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
customer_id | string | Sim | Seu identificador de usuário |
transaction_type | string | Sim | login, sign_up, deposit ou withdrawal |
transaction_name | string | Não | Rótulo legível para humanos |
payload.type | string | Sim | Sempre "encrypted" |
payload.data | string | Sim | O payload criptografado de collect() |
config.response.address | object | Não | { "type": "include" } para obter o endereço completo, omitido por padrão |
Config
O objeto config controla quais dados opcionais são incluídos na resposta.
{
"config": {
"response": {
"address": { "type": "include" }
}
}
}
| Campo | Valores | Padrão | Descrição |
|---|---|---|---|
config.response.address.type | "include" ou "omit" | "omit" | Se o endereço completo de geocodificação reversa (rua, cidade, estado, país, código postal) deve ser incluído na resposta. Requer dados GPS no payload. |
Quando address é omitido (padrão), o campo address na resposta será null mesmo se dados de localização foram coletados. Defina como "include" se seu backend precisa do endereço físico para conformidade, revisão de fraude ou exibição.
Autenticação
Use sua chave de API da Surt no cabeçalho Authorization como um token Bearer. Esta é uma chamada servidor para servidor - a chave de API nunca toca o dispositivo.
3. Tratar a resposta
O endpoint de avaliação retorna os mesmos dados que verify(), mas com detalhes completos. O campo address depende da sua config.
Com config.response.address.type: "include"
{
"status_code": 200,
"message": "Transaction evaluated successfully",
"data": {
"transaction": {
"transaction_id": "1775514112-f2e3034b891e...",
"created_at": "2026-04-06T22:21:52Z",
"customer_id": "user_123",
"transaction_type": "withdrawal",
"transaction_name": "User Payment",
"status": {
"type": "completed",
"risk_level": "low",
"result": {
"status": "accepted",
"review": false
},
"address": {
"street": "123 Main St",
"city": "San Francisco",
"state": "California",
"country": "United States",
"postal_code": "94103",
"formatted_address": "123 Main St, San Francisco, CA 94103, USA"
},
"signals": [ ... ],
"triggered_scenarios": [ ... ]
},
"device": {
"device_id": "fingerprint_abc",
"manufacturer": "Apple",
"model": "iPhone 15",
"os_version": "18.0"
},
"metadata": {
"device_locations": [ ... ],
"ip_locations": [ ... ],
"device_ids": [ ... ]
},
"network_threat": {
"status": "not_analyzed"
},
"country": "United States",
"ip_address": "203.0.113.50"
}
}
}
Com config.response.address.type: "omit" (padrão)
Mesma resposta, mas address é null:
{
"status_code": 200,
"message": "Transaction evaluated successfully",
"data": {
"transaction": {
"transaction_id": "1775514112-f2e3034b891e...",
"created_at": "2026-04-06T22:21:52Z",
"customer_id": "user_123",
"transaction_type": "withdrawal",
"transaction_name": "User Payment",
"status": {
"type": "completed",
"risk_level": "low",
"result": {
"status": "accepted",
"review": false
},
"address": null,
"signals": [ ... ],
"triggered_scenarios": [ ... ]
},
"device": {
"device_id": "fingerprint_abc",
"manufacturer": "Apple",
"model": "iPhone 15",
"os_version": "18.0"
},
"metadata": {
"device_locations": [ ... ],
"ip_locations": [ ... ],
"device_ids": [ ... ]
},
"network_threat": {
"status": "not_analyzed"
},
"country": "United States",
"ip_address": "203.0.113.50"
}
}
}
Seu backend pode usar status.risk_level, status.result.status, signals, triggered_scenarios e address para tomar sua própria decisão antes de responder ao cliente.
Substituição de localização
Assim como verify(), você pode substituir a coleta de localização por chamada:
// Coletar com localização (e resolver o IP do dispositivo via JWT)
const { payload } = await collect(jwt, { collectLocation: true });
// Coletar sem localização e sem um JWT (nenhuma chamada de rede para a Surt)
const { payload } = await collect(undefined, { collectLocation: false });
CollectResult
interface CollectResult {
/** Base64-encoded encrypted payload. Pass as payload.data in the evaluate request. */
payload: string;
}
O payload é criptografado e só pode ser descriptografado pelo backend da Surt. Ele contém a impressão digital do dispositivo, sinais de integridade do dispositivo, sinais de segurança e opcionalmente localização.
O resultado de collect() também inclui um objeto diagnostics opcional que descreve o que o SDK observou localmente - aditivo, seguro para ignorar:
const { payload, diagnostics } = await collect();
// diagnostics?.location: 'collected' | 'denied' | 'unavailable' | 'timeout' | 'not_requested'
// diagnostics?.networkIntel: 'collected' | 'unavailable'
// diagnostics?.warnings: { code, signal }[]
// diagnostics?.timings: { [fase]: ms } (somente Android, v0.5.2+)
Veja Códigos de Erro → Diagnóstico do resultado.
Exemplo de backend
- Node.js
- Java
- Python
app.post('/verify-device', async (req, res) => {
const { payload, userId } = req.body;
const surtResponse = await fetch(
'https://api.surt.com/geolocation/transactions/evaluate',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.SURT_API_KEY}`,
},
body: JSON.stringify({
customer_id: userId,
transaction_type: 'login',
payload: { type: 'encrypted', data: payload },
config: { response: { address: { type: 'include' } } },
}),
}
);
const { data } = await surtResponse.json();
const risk = data.transaction.status.risk_level;
const accepted = data.transaction.status.result.status === 'accepted';
res.json({ allowed: accepted, risk });
});
@PostMapping("/verify-device")
public ResponseEntity<Map<String, Object>> verifyDevice(@RequestBody Map<String, Object> body) {
String payload = (String) body.get("payload");
String userId = (String) body.get("userId");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + System.getenv("SURT_API_KEY"));
Map<String, Object> request = Map.of(
"customer_id", userId,
"transaction_type", "login",
"payload", Map.of("type", "encrypted", "data", payload),
"config", Map.of("response", Map.of("address", Map.of("type", "include")))
);
ResponseEntity<Map> response = restTemplate.exchange(
"https://api.surt.com/geolocation/transactions/evaluate",
HttpMethod.POST,
new HttpEntity<>(request, headers),
Map.class
);
Map data = (Map) response.getBody().get("data");
Map transaction = (Map) data.get("transaction");
Map status = (Map) transaction.get("status");
String risk = (String) status.get("risk_level");
Map result = (Map) status.get("result");
boolean accepted = "accepted".equals(result.get("status"));
return ResponseEntity.ok(Map.of("allowed", accepted, "risk", risk));
}
@app.post("/verify-device")
async def verify_device(request: Request):
body = await request.json()
surt_response = requests.post(
"https://api.surt.com/geolocation/transactions/evaluate",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['SURT_API_KEY']}",
},
json={
"customer_id": body["userId"],
"transaction_type": "login",
"payload": {"type": "encrypted", "data": body["payload"]},
"config": {"response": {"address": {"type": "include"}}},
},
)
data = surt_response.json()["data"]
risk = data["transaction"]["status"]["risk_level"]
accepted = data["transaction"]["status"]["result"]["status"] == "accepted"
return {"allowed": accepted, "risk": risk}