diff --git a/docs/models/Event.md b/docs/models/Event.md index 73fe0a78..27097e99 100644 --- a/docs/models/Event.md +++ b/docs/models/Event.md @@ -1,5 +1,10 @@ # Fingerprint.ServerSdk.Model.Event -Contains results from Fingerprint Identification and all active Smart Signals. Some Smart Signals are only supported for certain device types, these fields will be omitted for events not generated from the supported devices. Consult the [Smart Signals reference](https://docs.fingerprint.com/docs/smart-signals-reference) for more details. +Contains results from Fingerprint Identification and all active Smart Signals, or from the Automation Intelligence API. Check `source` to see which properties can be present: +- `device` - generated by the JS agent or a mobile SDK running on an end-user device. Contains Identification (`visitor_id`) and Smart Signals. Some Smart Signals are only supported for certain device types, these fields will be omitted for events not generated from the supported devices. +- `edge` - generated by the Automation Intelligence API (`/edge` endpoint). Contains only IP and bot analysis, never Identification or Smart Signals derived from device telemetry. + +Events stored before `source` was added don't have it; treat them as `device`. Consult the [Smart Signals reference](https://docs.fingerprint.com/docs/smart-signals-reference) for more details. + ## Properties diff --git a/res/fingerprint-server-api.yaml b/res/fingerprint-server-api.yaml index 531bdae8..cddd3495 100644 --- a/res/fingerprint-server-api.yaml +++ b/res/fingerprint-server-api.yaml @@ -1204,6 +1204,11 @@ components: enum: - device - edge + EnvironmentId: + type: string + examples: + - ae_47abaca3db2c7c43 + description: Environment ID of the event. Url: type: string examples: @@ -1720,11 +1725,6 @@ components: enum: - partially_completed - completed - EnvironmentId: - type: string - examples: - - ae_47abaca3db2c7c43 - description: Environment ID of the event. Suspect: type: boolean description: >- @@ -3146,11 +3146,24 @@ components: team](https://fingerprint.com/support/). Event: type: object - description: >- + description: > Contains results from Fingerprint Identification and all active Smart + Signals, or from the Automation Intelligence API. Check `source` to see + which properties can be present: + + - `device` - generated by the JS agent or a mobile SDK running on an + end-user device. Contains Identification (`visitor_id`) and Smart Signals. Some Smart Signals are only supported for certain device types, these fields will be omitted for events not generated from the supported - devices. Consult the [Smart Signals + devices. + + - `edge` - generated by the Automation Intelligence API (`/edge` + endpoint). Contains only IP and bot analysis, never Identification or + Smart Signals derived from device telemetry. + + + Events stored before `source` was added don't have it; treat them as + `device`. Consult the [Smart Signals reference](https://docs.fingerprint.com/docs/smart-signals-reference) for more details. required: diff --git a/src/Fingerprint.ServerSdk.Test/Api/FingerprintApiTests.cs b/src/Fingerprint.ServerSdk.Test/Api/FingerprintApiTests.cs index af2fc69f..f8607913 100644 --- a/src/Fingerprint.ServerSdk.Test/Api/FingerprintApiTests.cs +++ b/src/Fingerprint.ServerSdk.Test/Api/FingerprintApiTests.cs @@ -248,6 +248,54 @@ public async Task GetEventWithUnknownFieldAsyncTest() }); } + [Fact] + public async Task GetEventWithoutSourceAsyncTest() + { + // Events stored before `source` was added do not have it. + var json = ReadMockFile("events/get_event_200.json") + .Replace(" \"source\": \"device\",\n", "") + .Replace(" \"source\": \"device\",\r\n", ""); + Assert.DoesNotContain("\"source\"", json); + SetupMockResponseFromString(json); + + const string eventId = "1708102555327.NLOjmg"; + var response = await _instance.GetEventAsync(eventId); + + Assert.Multiple(() => + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True(response.TryOk(out var model)); + Assert.NotNull(model); + Assert.Equal(eventId, model.EventId); + Assert.Null(model.Source); + Assert.False(model.SourceOption.IsSet); + Assert.NotNull(model.Identification); + }); + } + + [Fact] + public async Task GetEdgeEventAsyncTest() + { + SetupMockResponse("events/get_event_with_edge_200.json"); + + const string eventId = "1758130560902.8tRtrH"; + var response = await _instance.GetEventAsync(eventId); + + Assert.Multiple(() => + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True(response.TryOk(out var model)); + Assert.NotNull(model); + Assert.IsType(model); + Assert.Equal(eventId, model.EventId); + Assert.Equal(EventSource.Edge, model.Source); + Assert.Null(model.Identification); + Assert.False(model.IdentificationOption.IsSet); + Assert.Equal("ChatGPT Agent", model.BotInfo.Name); + Assert.Equal("104.210.139.192", model.IpInfo.V4.Address); + }); + } + [Fact] public async Task GetEventAsyncError504() { diff --git a/src/Fingerprint.ServerSdk.Test/mocks/errors/400_edge_ip_required.json b/src/Fingerprint.ServerSdk.Test/mocks/errors/400_edge_ip_required.json new file mode 100644 index 00000000..8f055f8f --- /dev/null +++ b/src/Fingerprint.ServerSdk.Test/mocks/errors/400_edge_ip_required.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "request_cannot_be_parsed", + "message": "at least one of ipv4_address or ipv6_address is required" + } +} diff --git a/src/Fingerprint.ServerSdk.Test/mocks/errors/400_edge_unknown_field.json b/src/Fingerprint.ServerSdk.Test/mocks/errors/400_edge_unknown_field.json new file mode 100644 index 00000000..c0a153ef --- /dev/null +++ b/src/Fingerprint.ServerSdk.Test/mocks/errors/400_edge_unknown_field.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "request_cannot_be_parsed", + "message": "request body contains an unknown field \"unknown\"" + } +} \ No newline at end of file diff --git a/src/Fingerprint.ServerSdk.Test/mocks/errors/400_request_read_timeout.json b/src/Fingerprint.ServerSdk.Test/mocks/errors/400_request_read_timeout.json new file mode 100644 index 00000000..89daaca8 --- /dev/null +++ b/src/Fingerprint.ServerSdk.Test/mocks/errors/400_request_read_timeout.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "request_read_timeout", + "message": "request read timeout" + } +} \ No newline at end of file diff --git a/src/Fingerprint.ServerSdk.Test/mocks/errors/413_payload_too_large.json b/src/Fingerprint.ServerSdk.Test/mocks/errors/413_payload_too_large.json new file mode 100644 index 00000000..00a1c6f9 --- /dev/null +++ b/src/Fingerprint.ServerSdk.Test/mocks/errors/413_payload_too_large.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "payload_too_large", + "message": "payload too large" + } +} diff --git a/src/Fingerprint.ServerSdk.Test/mocks/events/get_event_with_edge_200.json b/src/Fingerprint.ServerSdk.Test/mocks/events/get_event_with_edge_200.json new file mode 100644 index 00000000..387c86eb --- /dev/null +++ b/src/Fingerprint.ServerSdk.Test/mocks/events/get_event_with_edge_200.json @@ -0,0 +1,51 @@ +{ + "event_id": "1758130560902.8tRtrH", + "timestamp": 1758130560902, + "source": "edge", + "url": "https://example.com/login?foo=bar", + "bot_info": { + "category": "ai_agent", + "provider": "OpenAI", + "provider_url": "https://openai.com", + "name": "ChatGPT Agent", + "identity": "signed", + "confidence": "high" + }, + "ip_info": { + "v4": { + "address": "104.210.139.192", + "geolocation": { + "accuracy_radius": 20, + "latitude": 29.42412, + "longitude": -98.49363, + "postal_code": "78205", + "timezone": "America/Chicago", + "city_name": "San Antonio", + "country_code": "US", + "country_name": "United States", + "continent_code": "NA", + "continent_name": "North America", + "subdivisions": [ + { + "iso_code": "TX", + "name": "Texas" + } + ] + }, + "asn": "8075", + "asn_name": "Microsoft Corporation", + "asn_network": "104.208.0.0/13", + "asn_type": "hosting", + "datacenter_result": true, + "datacenter_name": "Microsoft Azure" + } + }, + "proxy": false, + "proxy_confidence": "high", + "vpn": false, + "vpn_confidence": "medium", + "vpn_methods": { + "public_vpn": false, + "relay": false + } +} \ No newline at end of file diff --git a/src/Fingerprint.ServerSdk.Test/mocks/webhook/webhook_event.json b/src/Fingerprint.ServerSdk.Test/mocks/webhook/webhook_event.json index 35567917..c50663be 100644 --- a/src/Fingerprint.ServerSdk.Test/mocks/webhook/webhook_event.json +++ b/src/Fingerprint.ServerSdk.Test/mocks/webhook/webhook_event.json @@ -3,9 +3,14 @@ "tags": {}, "timestamp": 1708102555327, "event_id": "1708102555327.NLOjmg", + "source": "device", + "incremental_identification_status": "completed", "url": "https://www.example.com/login?hope{this{works[!", "ip_address": "61.127.217.15", "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....", + "device": "Other", + "os": "Windows", + "os_version": "7", "client_referrer": "https://example.com/blog/my-article", "browser_details": { "browser_name": "Chrome", @@ -26,14 +31,14 @@ "last_seen_at": 1708102555327 }, "supplementary_id_high_recall": { - "visitor_id": "3HNey93AkBW6CRbxV6xP", + "visitor_id": "0jnGMkPYXX37DqVa4ZIO3f_hr", "visitor_found": true, "confidence": { - "score": 0.97, - "version": "1.1" + "score": 0, + "version": "Not Supported" }, - "first_seen_at": 1708102555327, - "last_seen_at": 1708102555327 + "first_seen_at": 1778086556130, + "last_seen_at": 1778604975494 }, "proximity": { "id": "w1aTfd4MCvl", @@ -111,8 +116,10 @@ "last_seen_at": 1708102555327, "provider": "Massive" }, + "proxy_ml_score": 0.2, "vpn": false, "vpn_confidence": "high", + "vpn_ml_score": 0.2, "vpn_origin_timezone": "Europe/Berlin", "vpn_origin_country": "unknown", "vpn_methods": { @@ -120,10 +127,13 @@ "public_vpn": false, "auxiliary_mobile": false, "os_mismatch": false, - "relay": false + "relay": false, + "ml_prediction": false }, "incognito": false, "tampering": false, + "tampering_confidence": "high", + "tampering_ml_score": 0.3231, "tampering_details": { "anomaly_score": 0.1955, "anti_detect_browser": false @@ -143,6 +153,11 @@ "1_hour": 1, "24_hours": 1 }, + "distinct_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, "distinct_country": { "5_minutes": 1, "1_hour": 2, @@ -170,6 +185,11 @@ } }, "developer_tools": false, + "device_details": { + "device_manufacturer": "samsung", + "device_model": "SM-G991U", + "os_version": "13" + }, "mitm_attack": false, "sdk": { "platform": "js", @@ -187,8 +207,6 @@ }, "replayed": false, "high_activity_device": false, - "rare_device": false, - "rare_device_percentile_bucket": " - /// Contains results from Fingerprint Identification and all active Smart Signals. Some Smart Signals are only supported for certain device types, these fields will be omitted for events not generated from the supported devices. Consult the [Smart Signals reference](https://docs.fingerprint.com/docs/smart-signals-reference) for more details. + /// Contains results from Fingerprint Identification and all active Smart Signals, or from the Automation Intelligence API. Check `source` to see which properties can be present: - `device` - generated by the JS agent or a mobile SDK running on an end-user device. Contains Identification (`visitor_id`) and Smart Signals. Some Smart Signals are only supported for certain device types, these fields will be omitted for events not generated from the supported devices. - `edge` - generated by the Automation Intelligence API (`/edge` endpoint). Contains only IP and bot analysis, never Identification or Smart Signals derived from device telemetry. Events stored before `source` was added don't have it; treat them as `device`. Consult the [Smart Signals reference](https://docs.fingerprint.com/docs/smart-signals-reference) for more details. /// public partial class Event : IValidatableObject {