Pular para o conteúdo principal
Versão: Guardian v0.3.1

Collect (Server-to-Server)

v0.3.0+

Two Verification Paths

Guardian SDK offers two ways to verify devices. Choose based on your architecture:

verify()collect()
Who calls SurtThe SDK (from the device)Your backend (server-to-server)
JWT requirementRequired, fresh per callOptional (only for device IP)
Network calls from SDKYesOnly GET /geolocation/client-ip when a JWT is supplied
Your backend involvedNoYes
ResponseVerificationResult (allowed/denied)CollectResult (encrypted payload)
Best forSimple integration, frontend-driven decisionsBackend-driven decisions, custom logic, audit requirements

verify() flow

App → fetch fresh JWT from your backend → SDK.verify(jwt) → Surt backend → risk decision → App

The SDK calls Surt directly and returns allowed: true/false. Your app acts on the decision immediately. A fresh JWT is required for every call.

collect() flow

App → SDK.collect() → encrypted payload → App → Your backend → Surt /evaluate → Your backend → App

The SDK collects device data and encrypts it locally. With no arguments, collect() makes zero network calls to Surt. Your backend sends the payload to Surt's evaluate endpoint, receives the full risk assessment, and decides what to return to your app.

When to Use collect()

  • Your backend needs the risk data before responding to the client
  • You want to combine device risk with your own business logic server-side
  • You need full control over what the client sees
  • Compliance requires all third-party calls to originate from your infrastructure

Usage

1. Collect on the device

import { useGuardian } from '@surtai/guardian-rn';

function PaymentScreen() {
const { collect } = useGuardian();

const handlePayment = async () => {
// JWT is optional for collect(). Call collect() with no arguments,
// or pass collect(jwt) only if you want the device's public IP
// resolved into the payload.
const { payload } = await collect();

// Send payload to YOUR 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();
// Your backend already made the risk decision
};
}

For collect(), pass a JWT to collect(jwt) only if you want the device's public IP resolved into the payload; otherwise call collect() with no arguments. Unlike verify(), a collect() JWT may be reused or omitted - collect() generates its own attestation nonce.

2. Call Surt from your backend

Your backend receives the encrypted payload from the app, then calls Surt's evaluate endpoint:

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" }
}
}
}

Request fields

FieldTypeRequiredDescription
customer_idstringYesYour user identifier
transaction_typestringYeslogin, sign_up, deposit, or withdrawal
transaction_namestringNoHuman-readable label
payload.typestringYesAlways "encrypted"
payload.datastringYesThe encrypted payload from collect()
config.response.addressobjectNo{ "type": "include" } to get the full address, omitted by default

Config

The config object controls what optional data is included in the response.

{
"config": {
"response": {
"address": { "type": "include" }
}
}
}
FieldValuesDefaultDescription
config.response.address.type"include" or "omit""omit"Whether to include the full reverse-geocoded address (street, city, state, country, postal code) in the response. Requires GPS data in the payload.

When address is omitted (default), the address field in the response will be null even if location data was collected. Set to "include" if your backend needs the physical address for compliance, fraud review, or display.

Authentication

Use your Surt API key in the Authorization header as a Bearer token. This is a server-to-server call - the API key never touches the device.

3. Handle the response

The evaluate endpoint returns the same data as verify(), but with full detail. The address field depends on your config.

With 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"
}
}
}

With config.response.address.type: "omit" (default)

Same response, but address is 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"
}
}
}

Your backend can use status.risk_level, status.result.status, signals, triggered_scenarios, and address to make its own decision before responding to the client.

Location Override

Same as verify(), you can override location collection per call:

// Collect with location (and resolve device IP via the JWT)
const { payload } = await collect(jwt, { collectLocation: true });

// Collect without location and without a JWT (no Surt network call)
const { payload } = await collect(undefined, { collectLocation: false });

CollectResult

interface CollectResult {
/** Base64-encoded encrypted payload. Pass as payload.data in the evaluate request. */
payload: string;
}

The payload is encrypted and can only be decrypted by Surt's backend. It contains device fingerprint, attestation data, security signals, and optionally location.

Backend Example

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 });
});