From 48d8fc8f12d3e375b01be04b2c2b6b102d448001 Mon Sep 17 00:00:00 2001 From: Juraj Uhlar Date: Thu, 15 Oct 2026 22:29:57 +0100 Subject: [PATCH 1/3] chore: sync published v4 normalized schema and examples (v3.9.0 baseline) Reproduces the sync workflow against the currently published fingerprint-server-api-v4-normalized.yaml. Schema and generated code are unchanged; only examples that were missing from the repo are added. --- .../mocks/errors/400_edge_ip_required.json | 6 +++ .../mocks/errors/400_edge_unknown_field.json | 6 +++ .../errors/400_request_read_timeout.json | 6 +++ .../mocks/errors/413_payload_too_large.json | 6 +++ .../mocks/events/get_event_with_edge_200.json | 51 +++++++++++++++++++ 5 files changed, 75 insertions(+) create mode 100644 src/Fingerprint.ServerSdk.Test/mocks/errors/400_edge_ip_required.json create mode 100644 src/Fingerprint.ServerSdk.Test/mocks/errors/400_edge_unknown_field.json create mode 100644 src/Fingerprint.ServerSdk.Test/mocks/errors/400_request_read_timeout.json create mode 100644 src/Fingerprint.ServerSdk.Test/mocks/errors/413_payload_too_large.json create mode 100644 src/Fingerprint.ServerSdk.Test/mocks/events/get_event_with_edge_200.json 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 From 80b1ce9a90b325621df9d2066de0106337af8c2e Mon Sep 17 00:00:00 2001 From: Juraj Uhlar Date: Thu, 15 Oct 2026 22:30:06 +0100 Subject: [PATCH 2/3] chore: sync flat Event schema and examples from openapi#494 (78d51f2) Schema from fingerprint-server-api-v4-normalized.yaml built at fingerprintjs/openapi@78d51f2, filtered to the .NET SDK scopes, then regenerated with generate.sh. --- docs/models/Event.md | 7 ++- res/fingerprint-server-api.yaml | 27 ++++++--- .../mocks/webhook/webhook_event.json | 57 +++++++++++++++---- src/Fingerprint.ServerSdk/Model/Event.cs | 2 +- 4 files changed, 74 insertions(+), 19 deletions(-) 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/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 { From e2e4e944c92e02702d7e6ac7346ff91a24f79105 Mon Sep 17 00:00:00 2001 From: Juraj Uhlar Date: Thu, 15 Oct 2026 22:30:57 +0100 Subject: [PATCH 3/3] test: cover events without source and edge events (INTER-2457) --- .../Api/FingerprintApiTests.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) 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() {