Skip to content

Commit 72c80c8

Browse files
shyaankhanJu Hae Leessaouma
authored
adding sift custom destination function (#34)
* First sift push * update readme + prettier handler * Add Sift Decisions, WF, and update function names. * added changes * Added support for multiple sift response types Co-authored-by: Ju Hae Lee <ju.lee@Jus-MacBook-Pro.local> Co-authored-by: Sophie Saouma <ssaouma@siftscience.com>
1 parent 3758d7c commit 72c80c8

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed

destinations/sift/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Sift Custom Destination Function
2+
This example shows how to set up a custom destination function with [Sift](https://sift.com/), a solution that helps you prevent all types of online fraud and abuse. The destination not only sends events to Sift, but also takes the results from Sift and attaches the metric to a user's profile using the `identify` call.
3+
4+
## Setup
5+
- [ ] Create a [HTTP Source](https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#http%20tracking%20api)
6+
- [ ] Grab the REST API Key from Sift in your account
7+
8+
## How to use
9+
10+
Copy the `handler.js` code in this directory to your destination function
11+
12+
Make the necessary modifications in the `handler.js` to map your Segment data.
13+
14+
Replace the first line with your Segment write key that you generated from creating an HTTP Source
15+
16+
## Maintainers
17+
[@shyaankhan](https://github.com/shyaankhan) Segment
18+
[@ssaouma](https://github.com/ssaouma) Sift
19+
[@juhaelee](https://github.com/juhaelee) Segment

destinations/sift/handler.js

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Segment & Sift
2+
const SEGMENT_WRITE_KEY = '';
3+
const siftEndpoint = 'https://api.sift.com/v205/events';
4+
const endpointScore = siftEndpoint + '?return_score=true';
5+
const endpointWorkflow = siftEndpoint + '?return_workflow_status=true';
6+
7+
async function addScores(fields, res) {
8+
if (res.status == 0) {
9+
var scoreBody = {
10+
userId: fields.$user_id,
11+
traits: {
12+
contentAbuseScore: res.score_response.scores.content_abuse.score,
13+
paymentAbuseScore: res.score_response.scores.payment_abuse.score
14+
}
15+
};
16+
17+
const request = await fetch('https://api.segment.io/v1/identify', {
18+
body: JSON.stringify(scoreBody),
19+
headers: new Headers({
20+
Authorization: 'Basic ' + btoa(SEGMENT_WRITE_KEY + ':'),
21+
'Content-Type': 'application/json'
22+
}),
23+
method: 'post'
24+
});
25+
26+
return request.json();
27+
}
28+
}
29+
30+
async function addDecisions(fields, res) {
31+
var decisionBody = {
32+
userId: fields.$user_id,
33+
traits: {
34+
contentAbuseDecisions:
35+
res.score_response.workflow_statuses[0].history[0].config.decision_id,
36+
paymentAbuseDecisions:
37+
res.score_response.workflow_statuses[0].history[0].config.decision_id
38+
}
39+
};
40+
41+
const request = await fetch('https://api.segment.io/v1/identify', {
42+
body: JSON.stringify(decisionBody),
43+
headers: new Headers({
44+
Authorization: 'Basic ' + btoa(SEGMENT_WRITE_KEY + ':'),
45+
'Content-Type': 'application/json'
46+
}),
47+
method: 'post'
48+
});
49+
50+
return request.json();
51+
}
52+
53+
function getUrl(type) {
54+
if (type == 'SCORE') {
55+
return endpointScore;
56+
} else if (type == 'WORKFLOW') {
57+
return endpointWorkflow;
58+
} else {
59+
return siftEndpoint;
60+
}
61+
}
62+
63+
async function siftEventCall(fields, type) {
64+
const res = await fetch(getUrl(type), {
65+
body: JSON.stringify(fields),
66+
headers: { 'Content-Type': 'application/json' },
67+
method: 'post'
68+
});
69+
70+
const siftResponse = await res.json();
71+
72+
if (siftResponse.status <= 0) {
73+
// Please implement conditions for retries.
74+
} else if (siftResponse.status >= 0) {
75+
throw new InvalidEventPayload(siftResponse.error_message);
76+
}
77+
78+
var response;
79+
80+
if (type == 'SCORE') {
81+
response = await addScores(fields, siftResponse);
82+
} else if (type == 'WORKFLOW') {
83+
response = await addDecisions(fields, siftResponse);
84+
}
85+
86+
return response;
87+
}
88+
89+
async function onTrack(event, settings) {
90+
var fields = {};
91+
// Depending on when you want to call for a score, set the appropriate endpoint to hit.
92+
if (event.event == 'Signed Up') {
93+
fields = {
94+
$type: '$create_account',
95+
$user_id: event.userId,
96+
$name: event.properties.name,
97+
$user_email: event.properties.email,
98+
$ip: event.context.ip,
99+
$phone: event.properties.phone,
100+
$browser: {
101+
$user_agent: event.context.userAgent
102+
},
103+
$api_key: settings.apiKey
104+
};
105+
106+
return siftEventCall(fields, 'REGULAR');
107+
} else if (event.event == 'Signed In') {
108+
fields = {
109+
$type: '$login',
110+
$login_status: '$success',
111+
$user_id: event.userId,
112+
$username: event.properties.username,
113+
$ip: event.context.ip,
114+
$browser: {
115+
$user_agent: event.context.userAgent
116+
},
117+
$api_key: settings.apiKey
118+
};
119+
120+
return siftEventCall(fields, 'REGULAR');
121+
} else {
122+
return null;
123+
}
124+
}
125+
126+
async function onIdentify(event, settings) {
127+
// Depending on when you want to call for a score, set the appropriate endpoint to hit.
128+
129+
if (!event.userId) return;
130+
131+
var fields = {
132+
$type: '$update_account',
133+
$user_id: event.userId,
134+
$name: event.traits.name,
135+
$user_email: event.traits.email,
136+
$ip: event.context.ip,
137+
$phone: event.traits.phone,
138+
$browser: {
139+
$user_agent: event.context.userAgent
140+
},
141+
$api_key: settings.apiKey
142+
};
143+
144+
return siftEventCall(fields, endpoint);
145+
}

0 commit comments

Comments
 (0)