Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion example/getEvent.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ try {
const event = await client.getEvent(eventId, { ruleset_id: rulesetId })
console.log(JSON.stringify(event, null, 2))

if (rulesetId && event.rule_action) {
// Edge events have no rule action.
if (rulesetId && event.source !== 'edge' && event.rule_action) {
const { type, ruleset_id, rule_id, rule_expression } = event.rule_action
console.log(`Rule action: ${type} (ruleset: ${ruleset_id}, rule: ${rule_id}, expression: ${rule_expression})`)

Expand Down
17 changes: 13 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ client.getEvent('<eventId>').then((event) => {

// Get an event with a ruleset evaluation
client.getEvent('<eventId>', { ruleset_id: '<rulesetId>' }).then((event) => {
console.log(event.rule_action?.type) // 'allow' or 'block'
// Edge events (Automation Intelligence API) have no Identification or rule action.
if (event.source !== 'edge') {
console.log(event.rule_action?.type) // 'allow' or 'block'
}
})

// Search for identification events
Expand Down Expand Up @@ -151,12 +154,18 @@ Other `SdkError`s:

#### Webhook types

When handling [Webhooks](https://dev.fingerprint.com/reference/posteventwebhook#/) coming from Fingerprint, you can cast the payload as the built-in `Event` type:
When handling [Webhooks](https://dev.fingerprint.com/reference/posteventwebhook#/) coming from Fingerprint, you can cast the payload as the built-in `Webhook` type:

```ts
import { Event } from '@fingerprint/node-sdk'
import { Webhook } from '@fingerprint/node-sdk'

const webhook = eventWebhookBody as unknown as Webhook

const event = eventWebhookBody as unknown as Event
// Edge events (Automation Intelligence API) have no Identification.
// Bodies stored before `source` was added have no `source`; they are device events.
if (webhook.source !== 'edge') {
console.log(webhook.identification?.visitor_id)
}
```

#### Webhook signature validation
Expand Down
93 changes: 83 additions & 10 deletions resources/fingerprint-server-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,11 @@ components:
enum:
- device
- edge
EnvironmentId:
type: string
examples:
- ae_47abaca3db2c7c43
description: Environment ID of the event.
Url:
type: string
examples:
Expand Down Expand Up @@ -1597,6 +1602,52 @@ components:
description: >
`true` if the request came from a device running a VPN, `false`
otherwise.
EventEdge:
type: object
description: >-
An event generated by the Automation Intelligence API (`source` is
`edge`), containing IP and bot analysis for a request intercepted at the
edge. No client-side agent is involved, so Identification (`visitor_id`)
and Smart Signals derived from device telemetry are not available.
`ip_info` is always present, but is empty when no IP intelligence is
available for the request.
required:
- event_id
- timestamp
- source
- ip_info
properties:
event_id:
$ref: '#/components/schemas/EventId'
timestamp:
$ref: '#/components/schemas/Timestamp'
source:
$ref: '#/components/schemas/EventSource'
const: edge
linked_id:
$ref: '#/components/schemas/LinkedId'
environment_id:
$ref: '#/components/schemas/EnvironmentId'
tags:
$ref: '#/components/schemas/Tags'
url:
$ref: '#/components/schemas/Url'
bot_info:
$ref: '#/components/schemas/BotInfo'
ip_info:
$ref: '#/components/schemas/IPInfo'
proxy:
$ref: '#/components/schemas/Proxy'
proxy_confidence:
$ref: '#/components/schemas/ProxyConfidence'
proxy_details:
$ref: '#/components/schemas/ProxyDetails'
vpn:
$ref: '#/components/schemas/Vpn'
vpn_confidence:
$ref: '#/components/schemas/VpnConfidence'
vpn_methods:
$ref: '#/components/schemas/VpnMethods'
ErrorCode:
type: string
enum:
Expand Down Expand Up @@ -1706,11 +1757,6 @@ components:
enum:
- partially_completed
- completed
EnvironmentId:
type: string
examples:
- ae_47abaca3db2c7c43
description: Environment ID of the event.
Suspect:
type: boolean
description: >-
Expand Down Expand Up @@ -3130,18 +3176,21 @@ components:
case. This field is in the beta phase and only available to select
customers. If you are interested, please [contact our support
team](https://fingerprint.com/support/).
Event:
EventDevice:
type: object
description: >-
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
An event generated by the JS agent or a mobile SDK running on an
end-user device (`source` is `device`). 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.
required:
- event_id
- timestamp
- source
properties:
event_id:
$ref: '#/components/schemas/EventId'
Expand All @@ -3157,6 +3206,7 @@ components:
- browser
source:
$ref: '#/components/schemas/EventSource'
const: device
x-platforms:
- android
- ios
Expand Down Expand Up @@ -3485,6 +3535,29 @@ components:
- browser
- ios
- android
Event:
type: object
description: >
An event, discriminated by its `source` property. Check `source` before
reading other properties, because each source populates a different set
of them:

- `device` - an identification event generated by the JS agent or a
mobile SDK running on an end-user device.

- `edge` - an event generated by the Automation Intelligence API
(`/edge` endpoint), analyzing a request intercepted at the edge. It
contains only IP and bot analysis, and never contains Identification
(`visitor_id`) or Smart Signals derived from device telemetry.
required: []
oneOf:
- $ref: '#/components/schemas/EventDevice'
- $ref: '#/components/schemas/EventEdge'
discriminator:
propertyName: source
mapping:
device: '#/components/schemas/EventDevice'
edge: '#/components/schemas/EventEdge'
EventUpdate:
type: object
required: []
Expand Down
62 changes: 51 additions & 11 deletions src/generatedApiTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ export interface components {
* @enum {string}
*/
EventSource: 'device' | 'edge'
/**
* @description Environment ID of the event.
* @example ae_47abaca3db2c7c43
*/
EnvironmentId: string
/**
* @description Page URL from which the request was sent.
* @example https://www.example.com/login
Expand Down Expand Up @@ -419,6 +424,41 @@ export interface components {
/** @description `true` if the request came from a device running a VPN, `false` otherwise. */
ml_prediction?: boolean
}
/** @description An event generated by the Automation Intelligence API (`source` is `edge`), containing IP and bot analysis for a request intercepted at the edge. No client-side agent is involved, so Identification (`visitor_id`) and Smart Signals derived from device telemetry are not available. `ip_info` is always present, but is empty when no IP intelligence is available for the request. */
EventEdge: {
/** @description Unique identifier of the user's request. The first portion of the event_id is a unix epoch milliseconds timestamp. */
event_id: components['schemas']['EventId']
/** @description Timestamp of the event with millisecond precision in Unix time. */
timestamp: components['schemas']['Timestamp']
/**
* @description discriminator enum property added by openapi-typescript
* @enum {string}
*/
source: 'edge'
/** @description A customer-provided ID that was sent with the request. */
linked_id?: components['schemas']['LinkedId']
/** @description Environment ID of the event. */
environment_id?: components['schemas']['EnvironmentId']
/** @description A customer-provided value or an object that was sent with the identification request or updated later. */
tags?: components['schemas']['Tags']
/** @description Page URL from which the request was sent. */
url?: components['schemas']['Url']
/** @description Extended bot information. */
bot_info?: components['schemas']['BotInfo']
/** @description Details about the request IP address. Has separate fields for v4 and v6 IP address versions. */
ip_info: components['schemas']['IPInfo']
/** @description IP address was used by a public proxy provider or belonged to a known recent residential proxy */
proxy?: components['schemas']['Proxy']
/** @description Confidence level of the proxy detection. If a proxy is not detected, confidence is "high". If it's detected, can be "low", "medium", or "high". */
proxy_confidence?: components['schemas']['ProxyConfidence']
/** @description Proxy detection details (present if `proxy` is `true`) */
proxy_details?: components['schemas']['ProxyDetails']
/** @description VPN or other anonymizing service has been used when sending the request. */
vpn?: components['schemas']['Vpn']
/** @description A confidence rating for the VPN detection result — "low", "medium", or "high". Depends on the combination of results returned from all VPN detection methods. */
vpn_confidence?: components['schemas']['VpnConfidence']
vpn_methods?: components['schemas']['VpnMethods']
}
/**
* @description Error code:
* * `request_cannot_be_parsed` - The query parameters or JSON payload contains some errors
Expand Down Expand Up @@ -506,11 +546,6 @@ export interface components {
* @enum {string}
*/
IncrementalIdentificationStatus: 'partially_completed' | 'completed'
/**
* @description Environment ID of the event.
* @example ae_47abaca3db2c7c43
*/
EnvironmentId: string
/** @description Field is `true` if you have previously set the `suspect` flag for this event using the [Server API Update event endpoint](https://docs.fingerprint.com/reference/server-api-update-event). */
Suspect: boolean
Integration: {
Expand Down Expand Up @@ -1412,18 +1447,17 @@ export interface components {
*/
ml_score?: number
}[]
/** @description 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. */
Event: {
/** @description An event generated by the JS agent or a mobile SDK running on an end-user device (`source` is `device`). 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. */
EventDevice: {
/** @description Unique identifier of the user's request. The first portion of the event_id is a unix epoch milliseconds timestamp. */
event_id: components['schemas']['EventId']
/** @description Timestamp of the event with millisecond precision in Unix time. */
timestamp: components['schemas']['Timestamp']
/**
* @description Identifies how the event was generated.
* - `device` - the event was generated by the JS agent or a mobile SDK running on an end-user device.
* - `edge` - the event was generated by the Automation Intelligence API (`/edge` endpoint), analyzing a request intercepted at the edge.
* @description discriminator enum property added by openapi-typescript
* @enum {string}
*/
source?: components['schemas']['EventSource']
source: 'device'
/**
* @description Only included for requests using incremental identification.
* - `partially_completed` - Indicates this event corresponds to a 'minimal' request. Smart Signals, even if included in your plan, are not computed; hence, their values must be ignored.
Expand Down Expand Up @@ -1621,6 +1655,12 @@ export interface components {
/** @description Each label returns a prediction (true or false) for a specific use case (label field) based on a machine learning score. The machine learning score is determined by a model trained on customer data for that use case. This field is in the beta phase and only available to select customers. If you are interested, please [contact our support team](https://fingerprint.com/support/). */
labels?: components['schemas']['Labels']
}
/**
* @description An event, discriminated by its `source` property. Check `source` before reading other properties, because each source populates a different set of them:
* - `device` - an identification event generated by the JS agent or a mobile SDK running on an end-user device.
* - `edge` - an event generated by the Automation Intelligence API (`/edge` endpoint), analyzing a request intercepted at the edge. It contains only IP and bot analysis, and never contains Identification (`visitor_id`) or Smart Signals derived from device telemetry.
*/
Event: components['schemas']['EventDevice'] | components['schemas']['EventEdge']
EventUpdate: {
/**
* @description Linked ID value to assign to the existing event
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export {
Region,
SearchEventsFilter,
SearchEventsResponse,
Webhook,
} from './types'
export * from './sealedResults'
export * from './errors/unsealError'
Expand Down
12 changes: 12 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ export type SearchEventsResponse = components['schemas']['EventSearch']
*/
export type Event = components['schemas']['Event']

/**
* Body of a webhook sent by Fingerprint.
* Same as {@link Event}, except `source` is optional on device events: bodies stored before `source` was added
* don't have it, and resent webhooks deliver the stored body as-is. Check `source === 'edge'`; anything else is a
* device event.
*
* More info: https://dev.fingerprint.com/reference/posteventwebhook
*/
export type Webhook =
| (Omit<components['schemas']['EventDevice'], 'source'> & { source?: components['schemas']['EventDevice']['source'] })
| components['schemas']['EventEdge']

export type GetEventOptions = paths['/events/{event_id}']['get']['parameters']['query']

export type EventUpdate = components['schemas']['EventUpdate']
Expand Down
19 changes: 17 additions & 2 deletions tests/mocked-responses-tests/castWebhookEventTest.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { createHmac } from 'crypto'
import { describe, expect, it } from 'vitest'
import { Event } from '../../src'
import { Webhook, isValidWebhookSignature } from '../../src'
import eventWebhookBody from './mocked-responses-data/webhook/webhook_event.json'

describe('[Mocked body] Cast webhook event', () => {
it('with sample request body', () => {
const event = eventWebhookBody as Event
const event = eventWebhookBody as Webhook

// Assertion just to use the `event` variable. The goal of this test is to assume that Typescript won't throw an error here.
expect(event).toBeTruthy()
})

it('with a body that predates `source`', () => {
const oldBody: Record<string, unknown> = { ...eventWebhookBody }
delete oldBody.source
const data = Buffer.from(JSON.stringify(oldBody))
const secret = 'secret'
const header = `v1=${createHmac('sha256', secret).update(data).digest('hex')}`

expect(isValidWebhookSignature({ header, data, secret })).toEqual(true)

const event = JSON.parse(data.toString()) as Webhook
expect(event).not.toHaveProperty('source')
expect(event.event_id).toEqual(eventWebhookBody.event_id)
})
})
Loading
Loading