diff --git a/CHANGELOG.md b/CHANGELOG.md index 36209ab..601ce03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Unreleased + +- Scored webhook 2026-10-08 removes browser_vpn_proxy, datacenter_ip, abuser and suspicious_paid_click flags. vpn includes Browser VPN/Proxy in flags, connection type and signals; site_id remains. Legacy schemas remain readable. + # Changelog ## Unreleased diff --git a/dist/schemas/identification-scored-event.schema.json b/dist/schemas/identification-scored-event.schema.json index 62b7827..b2ec322 100644 --- a/dist/schemas/identification-scored-event.schema.json +++ b/dist/schemas/identification-scored-event.schema.json @@ -88,7 +88,10 @@ ], "properties": { "schema_version": { - "const": "2026-10-07" + "enum": [ + "2026-10-07", + "2026-10-08" + ] } }, "additionalProperties": true @@ -194,6 +197,92 @@ }, "additionalProperties": true } + }, + { + "if": { + "type": "object", + "properties": { + "schema_version": { + "const": "2026-10-08" + } + }, + "required": [ + "schema_version" + ] + }, + "then": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "connection_type": { + "type": "string", + "not": { + "const": "browser_vpn_proxy" + } + }, + "signals": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "not": { + "const": "browser_vpn_proxy" + } + } + } + } + }, + "detection_flags": { + "type": "object", + "not": { + "anyOf": [ + { + "type": "object", + "required": [ + "browser_vpn_proxy" + ], + "properties": { + "browser_vpn_proxy": {} + } + }, + { + "type": "object", + "required": [ + "datacenter_ip" + ], + "properties": { + "datacenter_ip": {} + } + }, + { + "type": "object", + "required": [ + "abuser" + ], + "properties": { + "abuser": {} + } + }, + { + "type": "object", + "required": [ + "suspicious_paid_click" + ], + "properties": { + "suspicious_paid_click": {} + } + } + ] + } + } + } + } + } + } } ], "$defs": { @@ -429,15 +518,12 @@ }, "DetectionFlags": { "type": "object", - "description": "Stable yes/no verdicts for the identification. Legacy 19 keys are always present; the extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on\nthe Risk Score; signal names are for display and logging.\n\nWhen `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and\n`javascript_disabled` are always `false`.", + "description": "Stable yes/no verdicts for the identification. The extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on\nthe Risk Score; signal names are for display and logging.\n\nWhen `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and\n`javascript_disabled` are always `false`.", "required": [ "vpn", "privacy_relay", - "browser_vpn_proxy", "tor", "proxy", - "datacenter_ip", - "abuser", "os_mismatch", "os_not_detected", "timezone_mismatch", @@ -446,7 +532,6 @@ "ip_mismatch", "incognito", "search_bot", - "suspicious_paid_click", "javascript_disabled", "stun_not_checked", "check_incomplete" @@ -454,7 +539,7 @@ "properties": { "vpn": { "type": "boolean", - "description": "A VPN was detected (scored `vpn` signal)." + "description": "A VPN or Browser VPN/Proxy was detected." }, "privacy_relay": { "type": "boolean", @@ -462,6 +547,7 @@ }, "browser_vpn_proxy": { "type": "boolean", + "deprecated": true, "description": "A VPN or proxy built into the browser or one of its extensions. `true` exactly when `connection_type` is `browser_vpn_proxy`." }, "tor": { @@ -474,10 +560,12 @@ }, "datacenter_ip": { "type": "boolean", + "deprecated": true, "description": "The public IP belongs to a datacenter or hosting range." }, "abuser": { "type": "boolean", + "deprecated": true, "description": "The public IP has a record of abuse in IP intelligence." }, "os_mismatch": { @@ -514,6 +602,7 @@ }, "suspicious_paid_click": { "type": "boolean", + "deprecated": true, "description": "The visit came from a paid ad click (Google Ads, Meta, TikTok, Microsoft Ads, LinkedIn, Pinterest or X) and the Risk Score is 60 or more (the 999 marker included)." }, "javascript_disabled": { @@ -640,7 +729,7 @@ }, "IdentificationScoredData": { "type": "object", - "description": "Final identification. risk_score is this scan only. Version 2026-10-07 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted.", + "description": "Final identification. risk_score is this scan only. Version 2026-10-08 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted.", "required": [ "request_id", "visitor_id", @@ -870,8 +959,10 @@ "SchemaVersion": { "type": "string", "minLength": 1, - "description": "Webhook contract version. Scored release 2026-10-06; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", + "description": "Webhook contract version. Scored release 2026-10-08; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", "examples": [ + "2026-10-08", + "2026-10-07", "2026-10-06", "2026-06-01" ] diff --git a/dist/schemas/identification.schema.json b/dist/schemas/identification.schema.json index cf5cd78..11c4995 100644 --- a/dist/schemas/identification.schema.json +++ b/dist/schemas/identification.schema.json @@ -382,15 +382,12 @@ }, "DetectionFlags": { "type": "object", - "description": "Stable yes/no verdicts for the identification. Legacy 19 keys are always present; the extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on\nthe Risk Score; signal names are for display and logging.\n\nWhen `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and\n`javascript_disabled` are always `false`.", + "description": "Stable yes/no verdicts for the identification. The extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on\nthe Risk Score; signal names are for display and logging.\n\nWhen `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and\n`javascript_disabled` are always `false`.", "required": [ "vpn", "privacy_relay", - "browser_vpn_proxy", "tor", "proxy", - "datacenter_ip", - "abuser", "os_mismatch", "os_not_detected", "timezone_mismatch", @@ -399,7 +396,6 @@ "ip_mismatch", "incognito", "search_bot", - "suspicious_paid_click", "javascript_disabled", "stun_not_checked", "check_incomplete" @@ -407,7 +403,7 @@ "properties": { "vpn": { "type": "boolean", - "description": "A VPN was detected (scored `vpn` signal)." + "description": "A VPN or Browser VPN/Proxy was detected." }, "privacy_relay": { "type": "boolean", @@ -415,6 +411,7 @@ }, "browser_vpn_proxy": { "type": "boolean", + "deprecated": true, "description": "A VPN or proxy built into the browser or one of its extensions. `true` exactly when `connection_type` is `browser_vpn_proxy`." }, "tor": { @@ -427,10 +424,12 @@ }, "datacenter_ip": { "type": "boolean", + "deprecated": true, "description": "The public IP belongs to a datacenter or hosting range." }, "abuser": { "type": "boolean", + "deprecated": true, "description": "The public IP has a record of abuse in IP intelligence." }, "os_mismatch": { @@ -467,6 +466,7 @@ }, "suspicious_paid_click": { "type": "boolean", + "deprecated": true, "description": "The visit came from a paid ad click (Google Ads, Meta, TikTok, Microsoft Ads, LinkedIn, Pinterest or X) and the Risk Score is 60 or more (the 999 marker included)." }, "javascript_disabled": { diff --git a/dist/schemas/webhook-event.schema.json b/dist/schemas/webhook-event.schema.json index d085438..18acd7a 100644 --- a/dist/schemas/webhook-event.schema.json +++ b/dist/schemas/webhook-event.schema.json @@ -305,15 +305,12 @@ }, "DetectionFlags": { "type": "object", - "description": "Stable yes/no verdicts for the identification. Legacy 19 keys are always present; the extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on\nthe Risk Score; signal names are for display and logging.\n\nWhen `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and\n`javascript_disabled` are always `false`.", + "description": "Stable yes/no verdicts for the identification. The extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on\nthe Risk Score; signal names are for display and logging.\n\nWhen `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and\n`javascript_disabled` are always `false`.", "required": [ "vpn", "privacy_relay", - "browser_vpn_proxy", "tor", "proxy", - "datacenter_ip", - "abuser", "os_mismatch", "os_not_detected", "timezone_mismatch", @@ -322,7 +319,6 @@ "ip_mismatch", "incognito", "search_bot", - "suspicious_paid_click", "javascript_disabled", "stun_not_checked", "check_incomplete" @@ -330,7 +326,7 @@ "properties": { "vpn": { "type": "boolean", - "description": "A VPN was detected (scored `vpn` signal)." + "description": "A VPN or Browser VPN/Proxy was detected." }, "privacy_relay": { "type": "boolean", @@ -338,6 +334,7 @@ }, "browser_vpn_proxy": { "type": "boolean", + "deprecated": true, "description": "A VPN or proxy built into the browser or one of its extensions. `true` exactly when `connection_type` is `browser_vpn_proxy`." }, "tor": { @@ -350,10 +347,12 @@ }, "datacenter_ip": { "type": "boolean", + "deprecated": true, "description": "The public IP belongs to a datacenter or hosting range." }, "abuser": { "type": "boolean", + "deprecated": true, "description": "The public IP has a record of abuse in IP intelligence." }, "os_mismatch": { @@ -390,6 +389,7 @@ }, "suspicious_paid_click": { "type": "boolean", + "deprecated": true, "description": "The visit came from a paid ad click (Google Ads, Meta, TikTok, Microsoft Ads, LinkedIn, Pinterest or X) and the Risk Score is 60 or more (the 999 marker included)." }, "javascript_disabled": { @@ -516,7 +516,7 @@ }, "IdentificationScoredData": { "type": "object", - "description": "Final identification. risk_score is this scan only. Version 2026-10-07 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted.", + "description": "Final identification. risk_score is this scan only. Version 2026-10-08 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted.", "required": [ "request_id", "visitor_id", @@ -732,7 +732,10 @@ ], "properties": { "schema_version": { - "const": "2026-10-07" + "enum": [ + "2026-10-07", + "2026-10-08" + ] } }, "additionalProperties": true @@ -838,6 +841,92 @@ }, "additionalProperties": true } + }, + { + "if": { + "type": "object", + "properties": { + "schema_version": { + "const": "2026-10-08" + } + }, + "required": [ + "schema_version" + ] + }, + "then": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "connection_type": { + "type": "string", + "not": { + "const": "browser_vpn_proxy" + } + }, + "signals": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "not": { + "const": "browser_vpn_proxy" + } + } + } + } + }, + "detection_flags": { + "type": "object", + "not": { + "anyOf": [ + { + "type": "object", + "required": [ + "browser_vpn_proxy" + ], + "properties": { + "browser_vpn_proxy": {} + } + }, + { + "type": "object", + "required": [ + "datacenter_ip" + ], + "properties": { + "datacenter_ip": {} + } + }, + { + "type": "object", + "required": [ + "abuser" + ], + "properties": { + "abuser": {} + } + }, + { + "type": "object", + "required": [ + "suspicious_paid_click" + ], + "properties": { + "suspicious_paid_click": {} + } + } + ] + } + } + } + } + } + } } ] }, @@ -1227,8 +1316,10 @@ "SchemaVersion": { "type": "string", "minLength": 1, - "description": "Webhook contract version. Scored release 2026-10-06; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", + "description": "Webhook contract version. Scored release 2026-10-08; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", "examples": [ + "2026-10-08", + "2026-10-07", "2026-10-06", "2026-06-01" ] diff --git a/dist/schemas/webhook-ping-event.schema.json b/dist/schemas/webhook-ping-event.schema.json index 449114d..f858f4f 100644 --- a/dist/schemas/webhook-ping-event.schema.json +++ b/dist/schemas/webhook-ping-event.schema.json @@ -41,8 +41,10 @@ "SchemaVersion": { "type": "string", "minLength": 1, - "description": "Webhook contract version. Scored release 2026-10-06; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", + "description": "Webhook contract version. Scored release 2026-10-08; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", "examples": [ + "2026-10-08", + "2026-10-07", "2026-10-06", "2026-06-01" ] diff --git a/dist/shieldlabs-api.json b/dist/shieldlabs-api.json index 1a95ca7..fff5271 100644 --- a/dist/shieldlabs-api.json +++ b/dist/shieldlabs-api.json @@ -2313,8 +2313,10 @@ "SchemaVersion": { "type": "string", "minLength": 1, - "description": "Webhook contract version. Scored release 2026-10-06; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", + "description": "Webhook contract version. Scored release 2026-10-08; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01.", "examples": [ + "2026-10-08", + "2026-10-07", "2026-10-06", "2026-06-01" ] @@ -2457,15 +2459,12 @@ }, "DetectionFlags": { "type": "object", - "description": "Stable yes/no verdicts for the identification. Legacy 19 keys are always present; the extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on\nthe Risk Score; signal names are for display and logging.\n\nWhen `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and\n`javascript_disabled` are always `false`.", + "description": "Stable yes/no verdicts for the identification. The extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on\nthe Risk Score; signal names are for display and logging.\n\nWhen `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and\n`javascript_disabled` are always `false`.", "required": [ "vpn", "privacy_relay", - "browser_vpn_proxy", "tor", "proxy", - "datacenter_ip", - "abuser", "os_mismatch", "os_not_detected", "timezone_mismatch", @@ -2474,7 +2473,6 @@ "ip_mismatch", "incognito", "search_bot", - "suspicious_paid_click", "javascript_disabled", "stun_not_checked", "check_incomplete" @@ -2482,7 +2480,7 @@ "properties": { "vpn": { "type": "boolean", - "description": "A VPN was detected (scored `vpn` signal)." + "description": "A VPN or Browser VPN/Proxy was detected." }, "privacy_relay": { "type": "boolean", @@ -2490,6 +2488,7 @@ }, "browser_vpn_proxy": { "type": "boolean", + "deprecated": true, "description": "A VPN or proxy built into the browser or one of its extensions. `true` exactly when `connection_type` is `browser_vpn_proxy`." }, "tor": { @@ -2502,10 +2501,12 @@ }, "datacenter_ip": { "type": "boolean", + "deprecated": true, "description": "The public IP belongs to a datacenter or hosting range." }, "abuser": { "type": "boolean", + "deprecated": true, "description": "The public IP has a record of abuse in IP intelligence." }, "os_mismatch": { @@ -2542,6 +2543,7 @@ }, "suspicious_paid_click": { "type": "boolean", + "deprecated": true, "description": "The visit came from a paid ad click (Google Ads, Meta, TikTok, Microsoft Ads, LinkedIn, Pinterest or X) and the Risk Score is 60 or more (the 999 marker included)." }, "javascript_disabled": { @@ -2687,7 +2689,7 @@ }, "IdentificationScoredData": { "type": "object", - "description": "Final identification. risk_score is this scan only. Version 2026-10-07 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted.", + "description": "Final identification. risk_score is this scan only. Version 2026-10-08 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted.", "required": [ "request_id", "visitor_id", @@ -2903,7 +2905,10 @@ ], "properties": { "schema_version": { - "const": "2026-10-07" + "enum": [ + "2026-10-07", + "2026-10-08" + ] } }, "additionalProperties": true @@ -3009,6 +3014,92 @@ }, "additionalProperties": true } + }, + { + "if": { + "type": "object", + "properties": { + "schema_version": { + "const": "2026-10-08" + } + }, + "required": [ + "schema_version" + ] + }, + "then": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "connection_type": { + "type": "string", + "not": { + "const": "browser_vpn_proxy" + } + }, + "signals": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "not": { + "const": "browser_vpn_proxy" + } + } + } + } + }, + "detection_flags": { + "type": "object", + "not": { + "anyOf": [ + { + "type": "object", + "required": [ + "browser_vpn_proxy" + ], + "properties": { + "browser_vpn_proxy": {} + } + }, + { + "type": "object", + "required": [ + "datacenter_ip" + ], + "properties": { + "datacenter_ip": {} + } + }, + { + "type": "object", + "required": [ + "abuser" + ], + "properties": { + "abuser": {} + } + }, + { + "type": "object", + "required": [ + "suspicious_paid_click" + ], + "properties": { + "suspicious_paid_click": {} + } + } + ] + } + } + } + } + } + } } ] }, diff --git a/dist/shieldlabs-api.yaml b/dist/shieldlabs-api.yaml index 09dcf54..616373a 100644 --- a/dist/shieldlabs-api.yaml +++ b/dist/shieldlabs-api.yaml @@ -1991,8 +1991,10 @@ components: SchemaVersion: type: string minLength: 1 - description: Webhook contract version. Scored release 2026-10-06; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01. + description: Webhook contract version. Scored release 2026-10-08; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01. examples: + - '2026-10-08' + - '2026-10-07' - '2026-10-06' - '2026-06-01' UserHid: @@ -2136,7 +2138,7 @@ components: DetectionFlags: type: object description: |- - Stable yes/no verdicts for the identification. Legacy 19 keys are always present; the extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on + Stable yes/no verdicts for the identification. The extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on the Risk Score; signal names are for display and logging. When `search_bot` is `true`, `incognito`, `check_incomplete`, `ip_mismatch` and @@ -2144,11 +2146,8 @@ components: required: - vpn - privacy_relay - - browser_vpn_proxy - tor - proxy - - datacenter_ip - - abuser - os_mismatch - os_not_detected - timezone_mismatch @@ -2157,19 +2156,19 @@ components: - ip_mismatch - incognito - search_bot - - suspicious_paid_click - javascript_disabled - stun_not_checked - check_incomplete properties: vpn: type: boolean - description: A VPN was detected (scored `vpn` signal). + description: A VPN or Browser VPN/Proxy was detected. privacy_relay: type: boolean description: A privacy relay such as iCloud Private Relay was detected. browser_vpn_proxy: type: boolean + deprecated: true description: A VPN or proxy built into the browser or one of its extensions. `true` exactly when `connection_type` is `browser_vpn_proxy`. tor: type: boolean @@ -2179,9 +2178,11 @@ components: description: A proxy was detected. datacenter_ip: type: boolean + deprecated: true description: The public IP belongs to a datacenter or hosting range. abuser: type: boolean + deprecated: true description: The public IP has a record of abuse in IP intelligence. os_mismatch: type: boolean @@ -2209,6 +2210,7 @@ components: description: A search-engine crawler. Its Risk Score is always 0. suspicious_paid_click: type: boolean + deprecated: true description: The visit came from a paid ad click (Google Ads, Meta, TikTok, Microsoft Ads, LinkedIn, Pinterest or X) and the Risk Score is 60 or more (the 999 marker included). javascript_disabled: type: boolean @@ -2313,7 +2315,7 @@ components: description: Three completed on-demand handler results. Technical errors block emission; no_history/skipped are explicit not_evaluated results. Anonymous checks are not_applicable. Multiaccount is separate. IdentificationScoredData: type: object - description: Final identification. risk_score is this scan only. Version 2026-10-07 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted. + description: Final identification. risk_score is this scan only. Version 2026-10-08 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted. required: - request_id - visitor_id @@ -2472,7 +2474,9 @@ components: - schema_version properties: schema_version: - const: '2026-10-07' + enum: + - '2026-10-07' + - '2026-10-08' additionalProperties: true then: type: object @@ -2543,6 +2547,56 @@ components: fingerprint: {} additionalProperties: true additionalProperties: true + - if: + type: object + properties: + schema_version: + const: '2026-10-08' + required: + - schema_version + then: + type: object + properties: + data: + type: object + properties: + connection_type: + type: string + not: + const: browser_vpn_proxy + signals: + type: array + items: + type: object + properties: + name: + type: string + not: + const: browser_vpn_proxy + detection_flags: + type: object + not: + anyOf: + - type: object + required: + - browser_vpn_proxy + properties: + browser_vpn_proxy: {} + - type: object + required: + - datacenter_ip + properties: + datacenter_ip: {} + - type: object + required: + - abuser + properties: + abuser: {} + - type: object + required: + - suspicious_paid_click + properties: + suspicious_paid_click: {} WebhookPingEvent: type: object description: Body of a `webhook.ping` delivery, sent when you verify an endpoint. It has no `data`. The keys arrive sorted alphabetically and `created_at` has second precision. diff --git a/spec/components/schemas/DetectionFlags.yaml b/spec/components/schemas/DetectionFlags.yaml index 175ee5a..7d20e79 100644 --- a/spec/components/schemas/DetectionFlags.yaml +++ b/spec/components/schemas/DetectionFlags.yaml @@ -1,7 +1,6 @@ type: object description: >- - Stable yes/no verdicts for the identification. Legacy 19 keys are always - present; the extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on + Stable yes/no verdicts for the identification. The extension flags depend on the scoring release. Task 204 adds optional ai_bot and ai_browser flags; older payloads omit them. Branch on these flags and on the Risk Score; signal names are for display and logging. @@ -14,11 +13,8 @@ description: >- required: - vpn - privacy_relay - - browser_vpn_proxy - tor - proxy - - datacenter_ip - - abuser - os_mismatch - os_not_detected - timezone_mismatch @@ -27,19 +23,19 @@ required: - ip_mismatch - incognito - search_bot - - suspicious_paid_click - javascript_disabled - stun_not_checked - check_incomplete properties: vpn: type: boolean - description: A VPN was detected (scored `vpn` signal). + description: A VPN or Browser VPN/Proxy was detected. privacy_relay: type: boolean description: A privacy relay such as iCloud Private Relay was detected. browser_vpn_proxy: type: boolean + deprecated: true description: A VPN or proxy built into the browser or one of its extensions. `true` exactly when `connection_type` is `browser_vpn_proxy`. tor: @@ -50,9 +46,11 @@ properties: description: A proxy was detected. datacenter_ip: type: boolean + deprecated: true description: The public IP belongs to a datacenter or hosting range. abuser: type: boolean + deprecated: true description: The public IP has a record of abuse in IP intelligence. os_mismatch: type: boolean @@ -85,6 +83,7 @@ properties: description: A search-engine crawler. Its Risk Score is always 0. suspicious_paid_click: type: boolean + deprecated: true description: The visit came from a paid ad click (Google Ads, Meta, TikTok, Microsoft Ads, LinkedIn, Pinterest or X) and the Risk Score is 60 or more (the 999 marker included). diff --git a/spec/components/schemas/IdentificationScoredData.yaml b/spec/components/schemas/IdentificationScoredData.yaml index a9122d6..0c47e46 100644 --- a/spec/components/schemas/IdentificationScoredData.yaml +++ b/spec/components/schemas/IdentificationScoredData.yaml @@ -1,5 +1,5 @@ type: object -description: Final identification. risk_score is this scan only. Version 2026-10-07 +description: Final identification. risk_score is this scan only. Version 2026-10-08 uses signals for score contributions, detection_flags for final decisions, and hre for account results. Older bodies remain accepted. required: diff --git a/spec/components/schemas/IdentificationScoredEvent.yaml b/spec/components/schemas/IdentificationScoredEvent.yaml index fb4b44d..08d8dbe 100644 --- a/spec/components/schemas/IdentificationScoredEvent.yaml +++ b/spec/components/schemas/IdentificationScoredEvent.yaml @@ -61,7 +61,7 @@ allOf: - schema_version properties: schema_version: - const: 2026-10-07 + enum: [2026-10-07, 2026-10-08] additionalProperties: true then: type: object @@ -129,3 +129,49 @@ allOf: fingerprint: {} additionalProperties: true additionalProperties: true + + - if: + type: object + properties: + schema_version: + const: 2026-10-08 + required: [schema_version] + then: + type: object + properties: + data: + type: object + properties: + connection_type: + type: string + not: + const: browser_vpn_proxy + signals: + type: array + items: + type: object + properties: + name: + type: string + not: + const: browser_vpn_proxy + detection_flags: + type: object + not: + anyOf: + - type: object + required: [browser_vpn_proxy] + properties: + browser_vpn_proxy: {} + - type: object + required: [datacenter_ip] + properties: + datacenter_ip: {} + - type: object + required: [abuser] + properties: + abuser: {} + - type: object + required: [suspicious_paid_click] + properties: + suspicious_paid_click: {} diff --git a/spec/components/schemas/SchemaVersion.yaml b/spec/components/schemas/SchemaVersion.yaml index a536a8b..907e6a7 100644 --- a/spec/components/schemas/SchemaVersion.yaml +++ b/spec/components/schemas/SchemaVersion.yaml @@ -1,7 +1,9 @@ type: string minLength: 1 -description: Webhook contract version. Scored release 2026-10-06; multi-account group release 2026-10-07; parsers also +description: Webhook contract version. Scored release 2026-10-08; multi-account group release 2026-10-07; parsers also accept legacy 2026-06-01. examples: + - 2026-10-08 + - 2026-10-07 - 2026-10-06 - 2026-06-01 diff --git a/test/build.test.mjs b/test/build.test.mjs index 4d2edb1..df8416a 100644 --- a/test/build.test.mjs +++ b/test/build.test.mjs @@ -10,9 +10,9 @@ const HISTORY_HOST = 'https://account.shieldlabs.ai'; const MANAGEMENT_HOST = 'https://api.shieldlabs.ai'; const FLAGS = [ - 'vpn', 'privacy_relay', 'browser_vpn_proxy', 'tor', 'proxy', 'datacenter_ip', 'abuser', + 'vpn', 'privacy_relay', 'tor', 'proxy', 'os_mismatch', 'os_not_detected', 'timezone_mismatch', 'anti_detect_browser', - 'browser_automation', 'ip_mismatch', 'incognito', 'search_bot', 'suspicious_paid_click', + 'browser_automation', 'ip_mismatch', 'incognito', 'search_bot', 'javascript_disabled', 'stun_not_checked', 'check_incomplete', ]; const WEBHOOK_DATA_KEYS = [ @@ -205,11 +205,11 @@ describe('document', () => { }); describe('schemas', () => { - it('require all 19 detection flags', () => { + it('require current detection flags and retain legacy properties', () => { const { doc } = freshBuild(); const flags = doc.components.schemas.DetectionFlags; assert.deepEqual([...flags.required].sort(), [...FLAGS].sort()); - assert.deepEqual(Object.keys(flags.properties).sort(), [...FLAGS, "os_mismatch2", "device_spoofing", "latency_test", "banned_ip", "ai_bot", "ai_browser"].sort()); + assert.deepEqual(Object.keys(flags.properties).sort(), [...FLAGS, "browser_vpn_proxy", "datacenter_ip", "abuser", "suspicious_paid_click", "os_mismatch2", "device_spoofing", "latency_test", "banned_ip", "ai_bot", "ai_browser"].sort()); for (const flag of FLAGS) assert.equal(flags.properties[flag].type, 'boolean', flag); }); diff --git a/test/contract-current.test.mjs b/test/contract-current.test.mjs index aca8838..c3a23c4 100644 --- a/test/contract-current.test.mjs +++ b/test/contract-current.test.mjs @@ -3,11 +3,22 @@ import {readFileSync} from 'node:fs'; import {describe,it} from 'node:test'; import {freshBuild,ROOT} from './helpers.mjs'; import path from 'node:path'; -describe('Core 2026-10-07 fixtures',()=>{ +describe('Core 2026-10-08 fixtures',()=>{ for(const name of ['named','anonymous','no-history','banned','incomplete'])it(name,()=>{ const fixture=JSON.parse(readFileSync(path.join(ROOT,'test/contracts',name+'.json'),'utf8')); const {doc,validator}=freshBuild();const schema=doc.components.schemas.IdentificationScoredEvent; const result=validator.validate(schema,fixture);assert.ok(result.valid,JSON.stringify(result.errors)); + for (const key of ['browser_vpn_proxy','datacenter_ip','abuser','suspicious_paid_click']) { + assert.equal(fixture.data.detection_flags[key],undefined); + const broken=structuredClone(fixture);broken.data.detection_flags[key]=false; + assert.equal(validator.validate(schema,broken).valid,false,key+' forbidden'); + } + for (const field of ['connection_type','signals']) { + const broken=structuredClone(fixture); + if (field==='connection_type') broken.data.connection_type='browser_vpn_proxy'; + else broken.data.signals=[{name:'browser_vpn_proxy',weight:30}]; + assert.equal(validator.validate(schema,broken).valid,false,field+' must unify VPN'); + } assert.equal(fixture.data.risk_events,undefined); assert.equal(fixture.data.fingerprint,undefined); assert.equal(fixture.data.detection_flags.os_mismatch2,undefined); diff --git a/test/contracts/ai-bot.json b/test/contracts/ai-bot.json new file mode 100644 index 0000000..e0a8330 --- /dev/null +++ b/test/contracts/ai-bot.json @@ -0,0 +1,87 @@ +{ + "event_id": "ce256c0f66536957adc5f719250f5b10ce331bd1", + "site_id": 7, + "event_type": "identification.scored", + "schema_version": "2026-10-08", + "created_at": "2026-10-06T00:00:02Z", + "data": { + "result_version": "2026-10-06T00:00:01Z", + "scoring_version": "core:fixture", + "ai_bot_owner": "OpenAI", + "hre": { + "account_sharing": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + }, + "account_takeover": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + }, + "impossible_travel": { + "cluster_id": null, + "status": "not_applicable", + "level": null, + "reason": "anonymous_user" + } + }, + "request_id": "13f84f05-7c2a-4e9b-9f1d-2a6b8c0e4d11", + "visitor_id": "161dfbad-8e7f-4a6b-9c5d-0e1f2a3b4c5d", + "device_id": "5eb7fd5c-2a1b-4c3d-9e8f-7a6b5c4d3e2f", + "session_id": "7a1b2c3d-4e5f-4789-abcd-ef0123456789", + "cookie_id": "3f2e1d0c-9b8a-4654-b210-fedcba987654", + "user_hid": "anonymous", + "domain": "example.com", + "public_ip": { + "ip": "203.0.113.1", + "country": "" + }, + "local_ip": { + "ip": "", + "country": "" + }, + "connection_type": "direct", + "os": "Windows", + "browser": "Chrome", + "device_type": "desktop", + "traffic_source": { + "channel": "", + "referrer_domain": "", + "landing_url": "", + "click_id_type": "", + "utm_source": "", + "utm_medium": "", + "utm_campaign": "", + "utm_content": "", + "utm_term": "" + }, + "risk_score": 0, + "signals": [], + "detection_flags": { + "device_spoofing": false, + "latency_test": false, + "banned_ip": false, + "vpn": false, + "privacy_relay": false, + "tor": false, + "proxy": false, + "os_mismatch": false, + "os_not_detected": false, + "timezone_mismatch": false, + "anti_detect_browser": false, + "browser_automation": false, + "ip_mismatch": false, + "incognito": false, + "search_bot": false, + "ai_bot": true, + "ai_browser": false, + "javascript_disabled": false, + "stun_not_checked": false, + "check_incomplete": false + }, + "observed_at": "2026-10-06T00:00:00Z" + } +} diff --git a/test/contracts/anonymous.json b/test/contracts/anonymous.json index e359a25..129a8d0 100644 --- a/test/contracts/anonymous.json +++ b/test/contracts/anonymous.json @@ -2,7 +2,7 @@ "event_id": "ce256c0f66536957adc5f719250f5b10ce331bd1", "site_id": 7, "event_type": "identification.scored", - "schema_version": "2026-10-07", + "schema_version": "2026-10-08", "created_at": "2026-10-06T00:00:02Z", "data": { "result_version": "2026-10-06T00:00:01Z", @@ -65,11 +65,8 @@ "banned_ip": false, "vpn": false, "privacy_relay": false, - "browser_vpn_proxy": false, "tor": false, "proxy": false, - "datacenter_ip": false, - "abuser": false, "os_mismatch": false, "os_not_detected": false, "timezone_mismatch": false, @@ -80,7 +77,6 @@ "search_bot": false, "ai_bot": false, "ai_browser": false, - "suspicious_paid_click": false, "javascript_disabled": false, "stun_not_checked": false, "check_incomplete": false diff --git a/test/contracts/banned.json b/test/contracts/banned.json index 7257f74..634847e 100644 --- a/test/contracts/banned.json +++ b/test/contracts/banned.json @@ -2,7 +2,7 @@ "event_id": "ce256c0f66536957adc5f719250f5b10ce331bd1", "site_id": 7, "event_type": "identification.scored", - "schema_version": "2026-10-07", + "schema_version": "2026-10-08", "created_at": "2026-10-06T00:00:02Z", "data": { "result_version": "2026-10-06T00:00:01Z", @@ -73,11 +73,8 @@ "banned_ip": true, "vpn": false, "privacy_relay": false, - "browser_vpn_proxy": false, "tor": false, "proxy": false, - "datacenter_ip": false, - "abuser": false, "os_mismatch": false, "os_not_detected": false, "timezone_mismatch": false, @@ -88,7 +85,6 @@ "search_bot": false, "ai_bot": false, "ai_browser": false, - "suspicious_paid_click": false, "javascript_disabled": false, "stun_not_checked": false, "check_incomplete": false diff --git a/test/contracts/incomplete.json b/test/contracts/incomplete.json index 301087a..b7d9147 100644 --- a/test/contracts/incomplete.json +++ b/test/contracts/incomplete.json @@ -2,7 +2,7 @@ "event_id": "ce256c0f66536957adc5f719250f5b10ce331bd1", "site_id": 7, "event_type": "identification.scored", - "schema_version": "2026-10-07", + "schema_version": "2026-10-08", "created_at": "2026-10-06T00:00:02Z", "data": { "result_version": "2026-10-06T00:00:01Z", @@ -73,11 +73,8 @@ "banned_ip": false, "vpn": false, "privacy_relay": false, - "browser_vpn_proxy": false, "tor": false, "proxy": false, - "datacenter_ip": false, - "abuser": false, "os_mismatch": false, "os_not_detected": false, "timezone_mismatch": false, @@ -88,7 +85,6 @@ "search_bot": false, "ai_bot": false, "ai_browser": false, - "suspicious_paid_click": false, "javascript_disabled": false, "stun_not_checked": true, "check_incomplete": true diff --git a/test/contracts/named.json b/test/contracts/named.json index 9d51b09..88157b2 100644 --- a/test/contracts/named.json +++ b/test/contracts/named.json @@ -2,7 +2,7 @@ "event_id": "ce256c0f66536957adc5f719250f5b10ce331bd1", "site_id": 7, "event_type": "identification.scored", - "schema_version": "2026-10-07", + "schema_version": "2026-10-08", "created_at": "2026-10-06T00:00:02Z", "data": { "result_version": "2026-10-06T00:00:01Z", @@ -68,11 +68,8 @@ "banned_ip": false, "vpn": false, "privacy_relay": false, - "browser_vpn_proxy": false, "tor": false, "proxy": false, - "datacenter_ip": false, - "abuser": false, "os_mismatch": false, "os_not_detected": false, "timezone_mismatch": false, @@ -83,7 +80,6 @@ "search_bot": false, "ai_bot": false, "ai_browser": false, - "suspicious_paid_click": false, "javascript_disabled": false, "stun_not_checked": false, "check_incomplete": false diff --git a/test/contracts/no-history.json b/test/contracts/no-history.json index f2f77c1..1e2e2af 100644 --- a/test/contracts/no-history.json +++ b/test/contracts/no-history.json @@ -2,7 +2,7 @@ "event_id": "ce256c0f66536957adc5f719250f5b10ce331bd1", "site_id": 7, "event_type": "identification.scored", - "schema_version": "2026-10-07", + "schema_version": "2026-10-08", "created_at": "2026-10-06T00:00:02Z", "data": { "result_version": "2026-10-06T00:00:01Z", @@ -68,11 +68,8 @@ "banned_ip": false, "vpn": false, "privacy_relay": false, - "browser_vpn_proxy": false, "tor": false, "proxy": false, - "datacenter_ip": false, - "abuser": false, "os_mismatch": false, "os_not_detected": false, "timezone_mismatch": false, @@ -83,7 +80,6 @@ "search_bot": false, "ai_bot": false, "ai_browser": false, - "suspicious_paid_click": false, "javascript_disabled": false, "stun_not_checked": false, "check_incomplete": false