Collect (Server-zu-Server)
Zwei Verifizierungswege
Das Guardian SDK bietet zwei Möglichkeiten zur Geräteverifizierung. Wählen Sie basierend auf Ihrer Architektur:
verify() | collect() | |
|---|---|---|
| Wer Surt aufruft | Das SDK (vom Gerät) | Ihr Backend (Server-zu-Server) |
| JWT-Anforderung | Erforderlich, frisch pro Aufruf | Optional (nur für die Geräte-IP) |
| Netzwerkaufrufe vom SDK | Ja | Nur GET /geolocation/client-ip, wenn ein JWT übergeben wird |
| Ihr Backend beteiligt | Nein | Ja |
| Antwort | VerificationResult (erlaubt/abgelehnt) | CollectResult (verschlüsseltes Payload) |
| Am besten für | Einfache Integration, Frontend-gesteuerte Entscheidungen | Backend-gesteuerte Entscheidungen, individuelle Logik, Audit-Anforderungen |
Ablauf von verify()
App → frisches JWT vom Backend holen → SDK.verify(jwt) → Surt backend → risk decision → App
Das SDK ruft Surt direkt auf und gibt allowed: true/false zurück. Ihre App reagiert sofort auf die Entscheidung. Für jeden Aufruf ist ein frisches JWT erforderlich.
Ablauf von collect()
App → SDK.collect() → encrypted payload → App → Your backend → Surt /evaluate → Your backend → App
Das SDK sammelt Gerätedaten und verschlüsselt sie lokal. Ohne Argumente macht collect() null Netzwerkaufrufe an Surt. Ihr Backend sendet das Payload an Surts Evaluate-Endpoint, erhält die vollständige Risikobewertung und entscheidet, was an Ihre App zurückgegeben wird.
Wann collect() verwenden
- Ihr Backend benötigt die Risikodaten, bevor es dem Client antwortet
- Sie möchten das Geräterisiko serverseitig mit Ihrer eigenen Geschäftslogik kombinieren
- Sie benötigen volle Kontrolle darüber, was der Client sieht
- Compliance erfordert, dass alle Drittanbieter-Aufrufe von Ihrer Infrastruktur ausgehen
Verwendung
1. Auf dem Gerät sammeln
import { useGuardian } from '@surtai/guardian-rn';
function PaymentScreen() {
const { collect } = useGuardian();
const handlePayment = async () => {
// Das JWT ist für collect() optional. Rufen Sie collect() ohne Argumente auf,
// oder übergeben Sie collect(jwt) nur, wenn die öffentliche IP des Geräts
// in das Payload aufgelöst werden soll.
const { payload } = await collect();
// Payload an IHR Backend senden
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();
// Ihr Backend hat die Risikoentscheidung bereits getroffen
};
}
Bei collect() übergeben Sie ein JWT an collect(jwt) nur, wenn die öffentliche IP des Geräts in das Payload aufgelöst werden soll; andernfalls rufen Sie collect() ohne Argumente auf. Anders als bei verify() darf ein collect()-JWT wiederverwendet oder weggelassen werden - collect() benötigt kein JWT pro Aufruf.
2. Surt von Ihrem Backend aufrufen
Ihr Backend empfängt das verschlüsselte Payload von der App und ruft dann Surts Evaluate-Endpoint auf:
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" }
}
}
}
Anforderungsfelder
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
customer_id | string | Ja | Ihr Benutzerbezeichner |
transaction_type | string | Ja | login, sign_up, deposit oder withdrawal |
transaction_name | string | Nein | Menschenlesbares Label |
payload.type | string | Ja | Immer "encrypted" |
payload.data | string | Ja | Das verschlüsselte Payload von collect() |
config.response.address | object | Nein | { "type": "include" } um die vollständige Adresse zu erhalten, standardmäßig ausgelassen |
Config
Das config-Objekt steuert, welche optionalen Daten in der Antwort enthalten sind.
{
"config": {
"response": {
"address": { "type": "include" }
}
}
}
| Feld | Werte | Standard | Beschreibung |
|---|---|---|---|
config.response.address.type | "include" oder "omit" | "omit" | Ob die vollständige reverse-geokodierte Adresse (Straße, Stadt, Bundesland, Land, Postleitzahl) in der Antwort enthalten sein soll. Erfordert GPS-Daten im Payload. |
Wenn address ausgelassen wird (Standard), ist das address-Feld in der Antwort null, auch wenn Standortdaten erfasst wurden. Setzen Sie es auf "include", wenn Ihr Backend die physische Adresse für Compliance, Betrugsüberprüfung oder Anzeige benötigt.
Authentifizierung
Verwenden Sie Ihren Surt-API-Schlüssel im Authorization-Header als Bearer-Token. Dies ist ein Server-zu-Server-Aufruf - der API-Schlüssel berührt niemals das Gerät.
3. Antwort verarbeiten
Der Evaluate-Endpoint gibt dieselben Daten wie verify() zurück, jedoch mit vollständigen Details. Das address-Feld hängt von Ihrer Config ab.
Mit 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"
}
}
}
Mit config.response.address.type: "omit" (Standard)
Gleiche Antwort, aber address ist 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"
}
}
}
Ihr Backend kann status.risk_level, status.result.status, signals, triggered_scenarios und address verwenden, um eine eigene Entscheidung zu treffen, bevor es dem Client antwortet.
Standortüberschreibung
Wie bei verify() können Sie die Standorterfassung pro Aufruf überschreiben:
// Mit Standort sammeln (und Geräte-IP über das JWT auflösen)
const { payload } = await collect(jwt, { collectLocation: true });
// Ohne Standort und ohne JWT sammeln (kein Surt-Netzwerkaufruf)
const { payload } = await collect(undefined, { collectLocation: false });
CollectResult
interface CollectResult {
/** Base64-encoded encrypted payload. Pass as payload.data in the evaluate request. */
payload: string;
}
Das Payload ist verschlüsselt und kann nur vom Surt-Backend entschlüsselt werden. Es enthält den Gerätefingerabdruck, Geräteintegritätssignale, Sicherheitssignale und optional den Standort.
Das collect()-Ergebnis enthält außerdem ein optionales diagnostics-Objekt, das beschreibt, was das SDK lokal beobachtet hat - additiv, kann ignoriert werden:
const { payload, diagnostics } = await collect();
// diagnostics?.location: 'collected' | 'denied' | 'unavailable' | 'timeout' | 'not_requested'
// diagnostics?.networkIntel: 'collected' | 'unavailable'
// diagnostics?.warnings: { code, signal }[]
// diagnostics?.timings: { [phase]: ms } (nur Android, v0.5.2+)
Siehe Fehlercodes → Ergebnis-Diagnose.
Backend-Beispiel
- 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}