Server libraries
An Upollo server library can be used identify users before they login, or if they visit from multiple devices.
The library runs on your server, and can be paired with a client library for web and mobile to protect logins and payments.
Availability
Upollo's server libraries are available for the following environments and languages:
- Go: Available via
go get github.com/upollo/userwatch-go
- NodeJS: Available on NPM via
npm install --save @upollo/node
- Python: Available via
pip install upollo-python
Get your API Keys
Sign up at upollo.ai/app/login to get your Private API key.
API
The Upollo
server API is outlined below. The details of the
request and response protocol buffers are documented in
upollo/userwatch-shepherd.
- Go
- NodeJS
- Python
// To use:
// import upollo "github.com/upollo/userwatch-go"
// client, err := upollo.NewClientBuilder(PRIVATE_API_KEY).Build()
package upollo
// An instance of ShepherdClient is used to access Upollo functionality.
// It is recommended to create and reuse a single instance. You will not
// typically need to specify any options.
func NewClientBuilder(publicApiKey string) *ClientOptions {}
func (*ClientOptions) Build() (ShepherdClient, error)
type ShepherdClient interface {
// Inform Upollo of an event in your application.
//
// Include any UserInfo you have in the TrackEventRequest, or an empty
// UserInfo if you have none.
Track(
ctx context.Context,
in *upollo.TrackEventRequest,
) *upollo.AnalysisResponse
// Access the assessment of a user for whom an event was previously
// registered with Upollo via a track(EventType, UserInfo) call from
// your client application.
//
// At this point you can also attach any additional UserInfo your server
// has which your client might not have had available.
Verify(
ctx context.Context,
in *upollo.VerifyRequest,
) *upollo.AnalysisResponse
// You can challenge the user for more identifying information. You can
// use either a phone SMS code, a WebAuthn login (like a fingerprint reader)
// or one you have built yourself which you verify.
CreateChallenge(
ctx context.Context,
in *upollo.CreateChallengeRequest,
) *upollo.CreateChallengeResponse
// You can verify the result of the challenge by passing a challenge verification
// request.
VerifyChallenge(
ctx context.Context,
in *upollo.ChallengeVerificationRequest
) (*upollo.ChallengeVerificationResponse, error)
}
// To use:
// import { Upollo } from "@upollo/node";
// const upolloClient = new Upollo("your-private-api_key");
export class Upollo {
// Accepts the project's private api key and an optional options object.
// 'options' can contain an optional 'url' to override the upollo api.
constructor(privateApiKey: string, options?: { url?: string });
// To verify a user you need a token from a web or mobile client library.
// You can also provide user info such as user id, email or phone number
// as a userInfo object to improve detection. If you don't have that info
// provide an empty userInfo.
verify(
eventToken: String,
userInfo?: uwproto_UserInfo,
challengeVerification?: uwproto_ChallengeVerificationRequest
): Promise<uwproto_AnalysisResponse>
// You can challenge the user for more identifying information. You can
// use either a phone SMS code, a WebAuthn login (like a fingerprint reader)
// or one you have built yourself which you verify. You can verify a challenge
// by doing another verify call providing a uwproto_ChallengeVerificationRequest
// to the VerifyRequest.
createChallenge(
type: uwproto_CreateChallengeType,
userinfo: uwproto_UserInfo,
deviceId: String,
origin: string
): Promise<uwproto_CreateChallengeResponse>
}
// To use:
// from upollo import upollo
// from upollo import upollo_public_pb2
// upolloClient = upollo.Upollo(privateApiKey)
class Upollo:
def __init__(self, privateApiKey):
// To verify a user you need a token from a web or mobile client library.
// You can also provide user info such as user id, email or phone number
// as a userInfo object to improve detection. If you don't have that info
// provide an empty userInfo.
def verify(self,
upolloToken: str,
userInfo: upollo_public_pb2.UserInfo
) -> upollo_public_pb2.AnalysisResponse:
// You can challenge the user for more identifying information. You can
// use either a phone SMS code, a WebAuthn login (like a fingerprint reader)
// or one you have built yourself which you verify.
def createChallenge(self
challengeType: upollo_public_pb2.ChallengeType,
userInfo: upollo_public_pb2.UserInfo,
deviceId: str
) -> upollo.upollo_shepherd_pb2.CreateChallengeResponse:
// You can verify the result of the challenge by passing a challenge verification
// request.
def verifyChallenge(
self,
type: upollo_public_pb2.ChallengeType,
userInfo: upollo_public_pb2.UserInfo,
deviceId: str,
challengeId: str,
secretResponse: upollo.upollo_shepherd_pb2.ChallengeVerificationRequest
) -> upollo.upollo_shepherd_pb2.ChallengeVerificationResponse
Examples
verify
This example shows how to offer the first month of your product free to a genuine new user, but a lesser discount and alternative messaging to someone who is signing up with their second or third email address in an attempt to get a second or third free month. A similar approach can be taken for various other ways you might tailor your offering.
The process here involves getting an Upollo token on your client, and then
verifying it on your server. Once the token is on your server, we check its validity and take a look at the analysis
of the user. If the MULTIPLE_ACCOUNTS
flag is not present we can confidently offer
them their first month free, whereas if the user is multi accounting we will try and
get them into the habit of paying, even if only a small amount to begin with. The snippet below assumes that the token has been sent
to the server in the request.
- Go
- NodeJS
- Python
func SelectOffer(
ctx context.Context,
client userwatchgo.ShepherdClient,
token String,
uid String,
email String
) String {
userInfo := &userwatchgo.UserInfo{
UserID: uid,
UserEmail: email,
}
analysis, err := client.Verify(ctx, &userwatchgo.VerifyRequest{
EventToken: token,
Userinfo: userInfo,
})
if err != nil {
if s, ok := status.FromError(err); ok && s.Code() == codes.InvalidArgument {
// If the token was invalid, this could be a malicious request.
return "multi_discount"
}
// Other errors suggest a system issue. Give benefit of the doubt.
return "free_month"
}
// Ensure the token was not maliciously swapped out for one from another event.
if analysis.GetEventType() != userwatchgo.EventType_REGISTER {
return "multi_discount"
}
for _, flag := range result.GetFlag() {
if flag.GetType() == userwatchgo.FlagType_MULTIPLE_ACCOUNTS {
return "multi_discount"
}
}
return "free_month"
}
async function selectOffer(client, token, uid, email) {
const userInfo = {
userID: uid,
userEmail: email,
};
client
.verify(token, userInfo)
.then((analysis) => {
const flagTypes = analysis.flags.map((x) => x.type);
if (
analysis.eventType == upollo.EventType.EVENT_TYPE_REGISTER &&
!flagTypes.includes(FlagType.FLAG_TYPE_MULTIPLE_ACCOUNTS)
) {
return "free_month";
} else {
return "multi_offer";
}
})
.catch((error) => {
// Give users the benefit of the dobut in case of system errors.
// TODO: Should distinguish INVALID_ARGUMENT errors as potential abuse.
return "free_month";
});
}
def select_offer(client, token, uid, email):
user_info = upollo_public_pb2.UserInfo(
user_id=uid,
user_email=email,
)
try:
analysis = client.verify(token, user_info)
flag_types = list(map(lambda f: f.type, analysis.flags))
if analysis.event_type != upollo_public_pb2.EVENT_TYPE_REGISTER
|| upollo_public_pb2.MULTIPLE_ACCOUNTS in flag_types:
return "multi_discount"
else:
return "free_month"
except grpc.RpcError as err:
if err and err.code() == grpc.StatusCode.INVALID_ARGUMENT:
# Token was invalid, could be a malicious request.
return "multi_discount"
else:
# Other errors suggest a system issue. Give benefit of the doubt.
return "free_month"