Intro
The PRIVO Platform ("the Platform") offers two "flavors" of integration, Headful and Headless. Headful integrations make use of PRIVO’s UI widgets to interact with the user while Headless integrations make use of PRIVO’s rich set of RESTful APIs. A typical integration will incorporate a mixture of both.
API Reference
The Platform API set is organized around REST. Our easy-to-use API has resource-oriented URLs, accepts JSON Syntax request bodies, returns JSON-Encoded responses and uses starndard based HTTP response codes, authentication and verbs.
PRIVO APIs differ for each partnership as we release new versions of the Platform and tailor functionality directly to Partner needs.
Environments
There are two available environments that can be accessed by Partner for use with the Platform. The API key pair ( credentials) used to authenicate the request determine which environment the request is for. To enable access to these environments, credentials must be supplied by a PRIVO Administrator.
Integration:
The Integration (INT) environment is to be used for all development and testing needs. This environment is the first step to integrating with the Platform. This environment is to be used for purposes of initial/on-going integration and unit testing. It will also be used to test product enhancements, configurations and updates. Integrations done in this environment do not affect your live data or interact with some third-party resources such as banking institutions (i.e. credit card or PayPal verifications).
Available End Points:
https://privohub-int.privo.com
https://api-gw-svc-int.privo.com
Production:
The Production (PROD) environment is to be used for production use only. Partners integrating into this environment must be signed off on their initial development into the Integration environment prior to being provided credentials to access Production. In an effort to keep integrity to the data, this environment is not to be used for unit testing or load testing without prior written consent from PRIVO management. Credentials used to access this environment must be kept safe and isolated from general use or non-essential team members and must be stored with utmost security by Partner at all times.
Available End Points:
https://privohub.privo.com
https://api-gw-svc.privo.com
Intro to OAuth 2.0
All API methods are protected through the OAuth 2.0 authorization protocol. This document does not attempt to explain the OAuth 2.0 protocol at a high-level. For a detailed understanding of the OAuth 2.0 specification, see OAuth 2.0 Authorization Protocol.
OAuth Basics:
The OAuth 2.0 authorization protocol enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf.
Every Partner must have a unique client ID and client secret in order to authenticate properly. A PRIVO Administrator will provide Partner with these values. Permissible grant types are unique to each Partner configuration. The PRIVO Administrator will work with each Partner to determine the appropriate grant types for Partner to use.
Required Parameters
Parameters:
- name: Site ID (siteId)
content: REQUIRED - Unique identifier directly related to the given service - name: Client Credentials
content: REQUIRED - (dependent on request) - Client ID and secret
All parameters and configurations are assigned by the PRIVO Administrator. The following paratmeters are available for use during API requests.
About OIDC
OpenID Connect 1.0 (OIC) is a simple identity layer on top of the OAuth 2.0 protocol. It enables clients to verify the identity of the end-user based on the authentication performed by an Authorization Server, as well as to obtain basic profile information about the end-user in an interoperable and REST-like manner.
The OIDC protocol, in abstract, follows the following steps.
- The RP (Client) sends a request to the OpenID Provider (OP).
- The OP authenticates the End-User and obtains authorization.
- The OP responds with an ID Token and usually an Access Token.
- The RP can send a request with the Access Token to the UserInfo Endpoint.
- The UserInfo Endpoint returns Claims about the End-User.
OIDC Code Flows
OpenID Connect authentication requests can follow one of three paths:
- Authorization Code Flow,
- Implicit Flow,
- Hybrid Flow,
The Authorization Code Flow is intended for clients that can securely maintain a client secret between themselves and the Authorization Server, whereas the Implicit Flow is intended for clients that cannot. However, the Authorization Code Flow is sometimes also used by native applications and other clients in order to be able to obtain a refresh token, even when they cannot ensure the secrecy of the client secret value. The Hybrid flow combines aspects of the Authorization Code flow and the Implicit flow. It enables clients to obtain an ID token and optionally an access token with only one round trip to the authorization server, possibly minimizing latency, while still enabling clients to later get tokens from the token endpoint. For more information about OpenID Connect, please refer to the OIDC Basic Client Implementer's Guide.
Various library implementations can be found here.
Regardless of the Code Flow choice, the first four steps are always the same. The difference between the various available flows is how the Authorization Server sends the End-User back to the Client and how the Client requests and/or validates the tokens.
Authorization Code Flow
The Authorization Code Flow goes through the following steps:
- Client prepares an Authentication Request containing the desired request parameters.
- Client sends the request to the Authorization Server.
- Authorization Server Authenticates the End-User.
- Authorization Server obtains End-User Consent/Authorization.
- Authorization Server sends the End-User back to the Client with an Authorization Code.
- Client requests a response using the Authorization Code at the Token Endpoint.
- Client receives a response that contains an ID Token and Access Token in the response body.
- Client validates the ID token and retrieves the End-User's Subject Identifier.
Authorization Code Flow Diagram
Implicit Code Flow
The Implicit Flow follows the following steps:
- Client prepares an Authentication Request containing the desired request parameters.
- Client sends the request to the Authorization Server.
- Authorization Server Authenticates the End-User.
- Authorization Server obtains End-User Consent/Authorization.
- Authorization Server sends the End-User back to the Client with an ID Token and, if requested, an Access Token.
- Client validates the ID token and retrieves the End-User's Subject Identifier.
Implicit Code Flow Diagram
Hybrid Code Flow
The Hybrid Flow follows the following steps:
- Client prepares an Authentication Request containing the desired request parameters.
- Client sends the request to the Authorization Server.
- Authorization Server Authenticates the End-User.
- Authorization Server obtains End-User Consent/Authorization.
- Authorization Server sends the End-User back to the Client with an Authorization Code and, depending on the Response Type, one or more additional parameters.
- Client requests a response using the Authorization Code at the Token Endpoint.
- Client receives a response that contains an ID Token and Access Token in the response body.
- Client validates the ID Token and retrieves the End-User's Subject Identifier.
Hybrid Code Flow Diagram
About Grant Types
An authorization grant is a credential representing the resource owner's authorization (to access its protected resources) used by the client to obtain an access token. Depending on the API resource, different grant types are used. The PRIVO Platform supports the following (3) three grant types: Implicit, Authorization Code and Client Credentials Authentication.
All examples make use of standard parameters state, nonce and redirect_uri. For detailed instructions pertaining to these parameters see OIC Authentication Request.
Implicit
The Implicit grant is a simplified authorization code flow optimized for clients implemented in a browser using a scripting language such as JavaScript. In the Implicit flow, instead of issuing the client an authorization code, the client is issued an access token directly.
When issuing an access token during the Implicit grant flow, the authorization server does not authenticate the client. In some cases, the client identity can be verified via the redirection URI used to deliver the access token to the client. The access token may be exposed to the resource owner or other applications with access to the resource owner's user-agent.
For additional information about Implicit grants, refer to Implicit Grant.
Upon successful user authorization a redirect to the supplied redirect_uri parameter supplied in the original authorization request. An access_token parameter will be supplied in the fragment part of the redirect_uri. Additional fragment parameters include token_type, id_token, expires_in and state. For a detailed explanation of these parameters, refer to Implicit Authentication Response.
The Userinfo endpoint may now be called to retrieve details about the User. For more information about /userinfo, refer to Userinfo Endpoint Information.
OpenID Connect authorization supports what is called id_token_hint. The value of the the id_token_hint parameter is the id_token previously issued by the PRIVO Authorization Server. Passing this values serves as a hint about the user's current or past authenticated session with the Client. If the user identified by the ID Token is logged in or is logged in by the request, then the Authorization Server returns a positive response; otherwise, it returns the error, login_required. id_token_hint must be present when prompt=none is used and an invalid_request error is returned if it is not.
Sample Implicit Authorization Request:
POST {{url}}/oauth/authorize?
response_type=id_token%20token
&client_id=someClientId
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb?
&scope=openid%20profile%20user_profile%20additional_info
&state=af0ifjsldkj
&nonce=n-0S6_WzA2Mj
Sample JSON Response:
HTTP/1.1 302 Found
Location:
https://client.example.org/cb?
access_token=eyJhbGciOiJSUzIi.......3gZthv7Y
&token_type=bearer
&id_token=eyJhbGciOiJSUzIi.......m5cr2cNNk
&expires_in=599
&state=af0ifjsldkj
Sample Request with ID Token:
POST {{url}}/oauth/authorize?
id_token_hint=eyJhbGciOiJSUzIi.......m5cr2cNNk
&prompt=none
&state=af0ifjsldkj
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb?
&response_type=id_token%20token
&client_id=someClientId
&scope=openid%20profile%20user_profile%20additional_info
Authorization Code
The Authorization Code grant is similar to the implicit grant type, however, there is an additional step that requires a user to "authorize" the partner access to their user data. For comparison, this flow is very similar to other well-known IDP authorization process's.
The authorization code is obtained by using an authorization server as an intermediary between the Client and Resource Owner. Instead of requesting authorization directly from the Resource Owner, the Client directs the Resource Owner to an authorization server, which in turn directs the Resource Owner back to the Client with the Authorization Code.
Before directing the Resource Owner back to the Client with the Authorization Code, the Authorization Server authenticates the Resource Owner and obtains authorization. Because the Resource Owner only authenticates with the Authorization Server, the Resource Owner's credentials are never shared with the Client. For more information, refer to Authorization Code Grant.
Once the access_token is returned, the Userinfo endpoint may now be called with the access_token to retrieve details about the User. For more information about /userinfo, refer to Userinfo Endpoint Information.
Sample Authorization Code Request:
POST {{url}}/oauth/authorize?
response_type=code
&client_id=someClientId
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb?
&scope=openid%20profile%20user_profile%20additional_info
&state=af0ifjsldkj
Sample JSON Response:
HTTP/1.1 302 Found
Location:
https://client.example.org/cb?
code=SplxlOBe....S6WxSbIA
&state=af0ifjsldkj
Sample Token Request
POST /auth/token HTTP/1.1
Host: {{url}}
Content-Type: application/json
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
grant_type=authorization_code
&code=SplxlOBe....S6WxSbIA
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb?
Sample JSON Response:
{
"access_token":"eyJhbGciOiJSUzIi.......3gZthv7Y",
"token_type":"Bearer",
"refresh_token": "8xLOxBtZp8",
"expires_in":599,
"id_token":"eyJhbGciOiJSUzIi.......m5cr2cNNk"
}
Client Credentials
Client Credentials can be used as an authorization grant when the authorization scope is limited to the protected resources under the control of the client or to protected resources previously arranged with the authorization server. Client credentials are used as an authorization grant typically when the client is requesting access to protected resources based on an authorization previously arranged with the authorization server. Depending on your specific Partner configuration or where there is only a notion of an "anonymous" user, the Client Credentials grant is used, requiring a valid client id/secret combination.
A partner can request an access token using only its client credentials when the client is requesting access to the protected resources under its control. For more information, refer to Client Credentials Grant.
The Userinfo endpoint may now be called with the access_token to retrieve details about the User. For more information about /userinfo, refer to Userinfo Endpoint Information.
Sample Client Credentials Authorization Request:
POST {{url}}/oauth/token?
client_id=someClientId
&client_secret=someClientSecret
&scope=PRIVOLOCK+TRUST
&grant_type=client_credentials
Sample JSON Response:
{
"access_token":"eyJhbGciOiJSUzIi.......3gZthv7Y",
"token_type":"Bearer",
"expires_in":599,
"scope":"PRIVOLOCK TRUST"
}
Authorization Endpoint
POST {{url}}/oauth/authorize
parameters:
- name: response_type
content: REQUIRED This value MUST becode
. This requests that both an Access Token and an ID Token be returned from the Token Endpoint in exchange for the code value returned from the Authorization Endpoint. - name: client_id
content: REQUIRED Client Identifier provided by PRIVO administrator. - name: scope
content: "OPTIONAL. Openid scope value. Supported the following scope values:openid
,profile
,email
,address
,phone
,user_profile
,additional_info
." - name: redirect_uri
content: REQUIRED The client Redirection URI to which the response will be sent. This URI must exactly match one of the pre-registered Redirection URI values. - name: state
content: RECOMMENDED. Opaque value used to maintain state between the request and the callback. - name: nonce
content: OPTIONAL. String value used to associate a Client session with an ID Token, and to mitigate replay attacks. - name: login_hint
content: OPTIONAL. Hint to the Authorization Server about the login identifier the End-User might use to log in (if necessary). - name: prompt
content: "Prompts the End-User for reauthentication and consent. Supported the following values:none
,login
,consent
,select_account
."
Start the authorization flow. For more information refer to Authorization Grant Documentation
Authorization Endpoint:
POST {{url}}/oauth/authorize
Sample Authorization Request:
{{url}}/oauth/authorize?
response_type=id_token%20token
&client_id=someClientId
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb?
&scope=openid%20profile%20user_profile%20additional_info
&state=af0ifjsldkj
&nonce=n-0S6_WzA2Mj
Sample JSON Response:
HTTP/1.1 302 Found
Location: https://client.example.org/cb?
access_token=eyJhbGciOiJSUzIi.......3gZthv7Y
&token_type=bearer
&id_token=eyJhbGciOiJSUzIi.......m5cr2cNNk
&expires_in=599
&state=af0ifjsldkj
Token Endpoint
POST {{url}}/oauth/token
parameters:
- name: grant_type content: "REQUIRED. Supported the following values:
authorization_code
,password
." - name: code content: REQUIRED. OAuth 2.0 Authorization Code.
- name: client_id content: REQUIRED. Client Identifier provided by PRIVO administrator.
- name: client_secret content: OPTIONAL (dependent on request type). Client Secret provided by PRIVO administrator.
- name: redirect_uri content: REQUIRED. The client Redirect URI to which the response will be sent. This URI must exactly match one of the pre-registered, white-listed Redirect URI values. content_markdown: |-
Returns the requested token. Token type depends on the grant type and scope(s) requested.
Token Endpoint:
POST {{url}}/oauth/token
Authorization: Bearer {access_token}
Sample Access Token Request:
{{url}}/oauth/token?client_id=partnerClientId&client_secret=partnerClientSecret&scope=PRIVOLOCK+TRUST&grant_type=client_credentials
Sample JSON Response:
{
"access_token": "eyJhbGciOiJSUzIi.......3gZthv7Y",
"token_type": "Bearer",
"expires_in": 599,
"scope": "PRIVOLOCK TRUST"
}
Userinfo Endpoint
GET {{url}}/userinfo
parameters:
- name: Authorization content: REQUIRED. Use Authorization header to send Bearer [access_token]
- name: Service ID (serviceId) content: REQUIRED. Identifier used to represent the given User
UserInfo Endpoint to retrieve information for the currently authenticated user. The request should use the HTTP GET method and the Access Token should be sent using the Authorization header. See https://openid.net/specs/openid-connect-basic-1_0.html#UserInfo for details.
In addition to the OpenID Connect standard scopes of profile, email, address, and phone, PRIVO provides additional scopes to request that specific sets of information be made available as Claim Values. For information about scopes, refer to the Scopes section of this documentation.
In addition to the OpenID Connect standard set of claims, (Standard Claims), PRIVO provides additional claims about the End-User. These claims, dependent on scope request, include the following:
Claim | Definition |
---|---|
consent_url | URLs for the consent widget. |
permissions | Feature permission details. |
verification_tier | Current verification level. |
minor | Is the User a minor? |
role_identifier | Role identifier for the User. |
approved | Has the User approved their account. |
shadow_account | Applies only to children and minors. |
site_token | Unique identifier that associates the current User with a site or service. |
activation_time | The time in seconds when the User's account was approved. |
parent_email | The email address for the current User's parent. Applies only to children. |
display_names | Array of displaynames for the User. |
consent_requests | Consent requests that are associated to the User. |
birthdate_time | User birth date time in seconds. |
registration_role | Role the User registered with. |
teen | Is the User a teen? |
teacher | Is the User a teacher? |
student | Is the User a student? |
pin | returns consent pin for every consent request related to the User. |
Userinfo Endpoint:
GET {{url}}/userinfo
Authorization: Bearer {access_token}
Sample Userinfo Request:
{{url}}/userinfo?serviceId=4a7578386......83550673d3d
Sample JSON Response:
{
"sub": "4a7578386......83550673d3d",
"role_identifier": "ROLE_XYZ",
"verification_tier": "G",
"minor": false,
"approved": true,
"shadow_account": false,
"site_token": "775a57627......714169312b",
"activation_time": 1448035241,
"parent_email": "someParentEmail@domain.com",
"birthdate": "1990-01-01",
"consent_urls": [
{
"first_name": "Mickey",
"consent_url": "{{url}}/e/r/a?token=445333.....513d3d"
}
],
"permissions": [
{
"on": true,
"consent_date": 1448035241,
"request_date": 1448035241,
"feature_active": true,
"feature_id": 99999,
"feature_identifier": "FEATURE_XYZ",
"feature_name": "A very special feature"
}
],
"address": {
"region": "FL",
"locality": "Tampa",
"country": "US",
"formatted": "100 Some St Tampa FL, 32810",
"postal_code": "32810",
"street_address": "100 Some St"
},
"email": "someUserEmail@domain.com",
"email_verified": true,
"family_name": "Moose",
"gender": "male",
"given_name": "Mickey",
"locale": "en_US",
"middle_name": "V",
"name": "Mickey Moose",
"nickname": "mmoose123",
"phone_number": "+1 (000) 0000-0000",
"phone_number_verified": false,
"picture": "{{url}}/images/avatar/PRIVOdogGus.jpg",
"preferred_username": "mmoose123",
"profile": "/api/account/4a7578386......83550673d3d",
"updated_at": "1448035241",
"website": "/api/account/4a7578386......83550673d3d",
"zoneinfo": "America/New_York"
}
Logout Endpoint
POST /logout
parameters:
- name: id_token_hint content: OPTIONAL. Previously issued ID Token passed to the logout endpoint as a hint about the user’s current authenticated session with the Client. This is used as an indication of the identity of the user that the partner is requesting be logged out by PRIVO.
- name: post_logout_redirect_uri content: OPTIONAL. URL to which the partner is requesting that the user’s browser be redirected after a logout has been performed. The value MUST have been previously registered with PRIVO, either using the post_logout_redirect_uris Registration parameter or via another mechanism. If supplied, PRIVO SHOULD honor this request following the logout. This uri must be one of the configured redirect URIs for the given partner.
- name: state content: OPTIONAL. Opaque value used by the partner to maintain state between the logout request and the callback to the endpoint specified by the post_logout_redirect_uri query parameter. If included in the logout request, PRIVO passes this value back to the partner using the state query parameter when redirecting the user’s browser back to the partner.
When a user requests to sign out of the Partner site that user's PRIVO OpenID Connect session must also be ended. The " Logout of Partner Service" sample code demonstrates how the logout process can he handled when a user signs out of partner website.
A partner can notify PRIVO that the user has logged out of the site, and might want to log out of PRIVO as well. In this case, the partner, after having logged the user out of the partner, redirects the user’s browser to PRIVO’s logout endpoint URL, /logout.
Logout Endpoint:
{{url}}/logout?
id_token_hint=eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMy...
&state=af0ifjsldkj
post_logout_redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
Logout of Partner Service:
<iframe id="privoHubSignOut" src="" class="hide"><!-- For ending PRIVO session --></iframe>
<script>
$(function () {
//.signout is the button on partner page
$('.signout').on('click', function (e) {
e.preventDefault();
try {
if ('localStorage' in window && window['localStorage'] !== null) {
//Clear local store on logout
sessionStorage.clear();
}
} catch (e) {
//do nothing
}
$('#privoHubSignOut').attr('src', '${privowebURL}/logout');
$('#privoHubSignOut').load(function () {
// j_spring_security_logout is the Partners endpoint
window.location = '/j_spring_security_logout';
});
});
});
</script>
Scopes
Clients use scope values to specify what access privileges are being requested for Access Tokens. The scopes associated with Access Tokens determine what resources will be available when they are used to access OAuth 2.0 protected endpoints. For OpenID Connect, scopes can be used to request that specific sets of information be made available as Claim Values. For an understanding of how scopes are used, see Scope Values.
In addition to the OpenID Connect standard scopes of openid, profile, email, offline_access, address, and phone, PRIVO provides additional scopes to request that specific sets of information be made available as Claim Values.
Scope | Definition |
---|---|
PRIVOLOCK | Used for API access. |
TRUST | Verifies entity is a trusted partner. |
user_profile | Provides attributes and display names of the User. |
additional_info | Provides permissions, role information, site token, shadow account and verification tier for the User. |
trust_email | Trusted partnership to allow email to be flagged as "verified". Requires use case and contract approval. |
delete_account | Removes accounts entirely from the system. Requires use case and contract approval. |
shadow | Creates shadow account User |
consent_url | Displays consent URL associated to the given request. Requires use case and contract approval. |
ID Token
The ID Token is a security token that contains Claims about the authentication of an End-User by an Authorization Server when using a Client, and potentially other requested Claims. The ID Token is represented as a JSON Web Token (JWT).
Sample JSON Response with ID Token:
{
"access_token":"eyJhbGciOiJSUzIi.......3gZthv7Y",
"token_type":"Bearer",
"expires_in":599,
"scope":"PRIVOLOCK TRUST openid",
"id_token":"eyJhbGciOiJSUzIi.......m5cr2cNNk"
}
Access Token
To request an Access Token, Partner must invoke the OAuth Token Endpoint using their unique client ID and secret in conjunction to the proper scopes and grants. The JSON response will deliver a 200 ok status with provided 'access_token' value. The 'access_token' value must be passed to all subsequent API calls in the Authorization Request Header.
Access Tokens granted to clients can be valid for up to 7 days from the date of their request, although standard iniated time is set for 10 minutes. Additionally, at any time, the PRIVO server can force expiration of an Access Token, thereby, forcing a Client to refresh its Access Token. In the event that an Access Token has expired for a protected resource, the Client will receive an error message defining the expired result. See "Expired Token Error Response" example in Refresh Token section.
For more information please follow this link.
Sample Token Request:
{{url}}/token?client_id=partnerClientId&client_secret=partnerClientSecret&scope=PRIVOLOCK+TRUST&grant_type=client_credentials
Sample JSON Response:
{
"access_token":"eyJhbGciOiJSUzIi.......3gZthv7Y",
"token_type":"Bearer",
"expires_in":3599,
"scope":"PRIVOLOCK TRUST"
}
Subsequent Request Header:
Authorization: Bearer eyJhbGciOiJSUzIi.......3gZthv7Y
Refresh Token
Refresh Tokens are issued to the Client by the Authorization Server and are used to obtain a new Access Token when the current Access Token becomes invalid or expires, or to obtain additional Access Tokens with identical or narrower scope (Access Tokens may have a shorter lifetime and fewer permissions than authorized by the Resource Owner). Issuing a Refresh Token is optional at the discretion of the Authorization Server. If the Authorization Server issues a Refresh Token, then it is included when issuing an Access Token.
Unlike Access Tokens, Refresh Tokens are intended for use only with Authorization Servers and are never sent to Resource Servers.
There are two options - Client Credential Flow and Authorized User Flow.
Option #1 - Authorization (Client Credential Flow)
For the client_credentials flow - add offline_access scope to the regular token request.
Example request/response:
POST [Endpoint]/token?client_id=[Client_ID]&client_secret=[Client_Secret]&scope=openid%20offline_access&grant_type=client_credentials HTTP/1.1
Host: privohub-int.privo.com
Content-Type: application/json
...
HTTP/1.1 200 OK
{
"access_token": "eyJraWQiOiJyc2ExIiw...wqU5RaTddjD2QlTgptP_g",
"token_type": "Bearer",
"expires_in": 599,
"refresh_token": "eyJhbGciOiJub25...OTFkNy02MzA4MjU2OGEyNTkifQ",
"scope": "openid offline_access"
}
Exchange your refresh token to access_token
at any time accessing PRIVO /token
API endpoint. All you need is just
your client credentials, specify grant_type = refresh_token
and provide refresh_token itself:
Example request/response:
POST [Endpoint]/token?client_id=[Client_ID]&client_secret=[Client_Secret]&grant_type=refresh_token&
refresh_token=eyJhbGciOiJub25lIn0.eyJleH...LTVlYTcwMjI2OWIyYyJ9. HTTP/1.1
Host: privohub-int.privo.com
Content-Type: application/x-www-form-urlencoded
...
{
"access_token": "eyJraWQiOiJyc2ExIiw...wqU5RaTddjD2QlTgptP_g",
"token_type": "Bearer",
"expires_in": 599,
"refresh_token": "eyJhbGciOiJub25...OTFkNy02MzA4MjU2OGEyNTkifQ",
"scope": "openid offline_access"
}
Option #2 - Authorization (OIDC Authorization Code Flow)
To issue a refresh token for user authentication instead of the client the restriction will be with ** authorization_code** flow. NOTE: By design in OpenId Implicit flow does not support refresh tokens.
Example below is using the INTEGRATION (INT) environment
First, redirect the user to the authentication page:
https://privohub-int.privo.com/authorize?client_id=[Client_ID]&response_mode=query&mode=NORMAL&response_type=code&
scope=TRUST%20openid%20profile%20user_profile%20additional_info%20offline_access&redirect_uri=[Encoded_Redirect_URI]
Authorization page can be customized by providing additional request parameters for example: mode - may be SIMPLE, NORMAL, ADVANCED state - any random characters you want. It will be returned back to your redirect_uri
Since offline_access
isn't considered as a standard OpenId scope, once signed in user will be prompted to approve
scope claims (or PRIVO can white list such scopes) and when user clicks Authorize button they will be redirected to
the defined redirect_uri with authorization code as a request parameter (see example below).
Example
Browser URL: https://www.someredirecturi.com/?code=kfec84
Second, exchange the authorization code for access and refresh tokens - Notice grant_type=authorization_code
:
POST [Endpoint]/token?client_id=[Client_ID]&client_secret=[Client_Secret]&redirect_uri=[Encoded_Redirect_URI]&
grant_type=authorization_code&code=kfec84 HTTP/1.1
Host: privohub-int.privo.com
Content-Type: application/json
...
{
"access_token": "eyJraWQiOiJyc2ExIiw...4Ikwt-HFwWr_W6H6qICA",
"token_type": "Bearer",
"expires_in": 599,
"refresh_token": "eyJhbGciOiJub25lI...WMtODU3OS01YzExYjYyZDQxMDYifQ.",
"scope": "user_profile additional_info openid offline_access profile",
"id_token": "eyJraWQiOiJyc2ExIiw...1JGZOpP3eQf1VqC9K9o7uE"
}
Response will contain an access_token
that can be used immediately and a refresh_token
that can be exchanged at any
time (follow Option#1 example above by calling /token
). NOTE: By default refresh token never expires.
Expired Token Error Response:
{
"error": "invalid_token",
"error_description": "Invalid access token: eyJhbGciOiJSUzIi...3gZthv7Y"
}
Request Information
- OAuth2 authentication is required
- All methods (except GET methods) accept JSON formatted data. All requests with a body (POST, PUT) MUST
set
Content-Type: application/json
andAccept: application/json
HTTP request headers accordingly. - All methods return JSON formatted data, even in cases where non-2xx status codes are returned.
In addition, all responses will have a standard wrapper that indicates more detailed information on the operation.
Response Wrapper :
{
"message":"Ok", //detailed status message
"status":"success", //success or fail
"entity":[ /* the actual data returned by the operation; may be scalar or
array, depending on the method */ ],
"validationErrors": [ /* any errors on the inputs */ ]
"resultCount": 0, /* if entity is a collection, number of objects in the collection */
"totalCount": 0 /* total number of objects matching the request, if entity is
a collection, in order to support paging */
}
Error Codes
Our Client libraries can raise exceptions for many reasons, such as a failed request, invalid parameters, authentication errors, and network unavailability. We recommend writing code that gracefully handles all possible API exceptions.
A Partner PRIVO Platform integration might have to deal with errors at some point when making API requests. These errors fall into a few major categories:
- Content Error - These errors occur because the content in the API request was invalid in some way. They return an HTTP response with a 4xx error code. For example, the API servers might return a '401' if an invalid API key was provided, or a '400' if a required parameter was missing.
- Network Error - These errors occur as a result of intermittent communication problems between the Client and Server. They return low-level errors, like socket or timeout exceptions. For example, a Client might time out while trying to read from PRIVO's Servers, or an API response might never be received because a connection terminates prematurely. NOTE: A network error wouldn't necessarily have otherwise been a successful request. It can also be another type of error that's been cloaked by an intermittent problem.
- Server Error - These errors occur because of a problem on PRIVO's Servers. Server errors return an HTTP response with a 5xx error code. PRIVO works to make these errors as rare as possible, but integrations should have a plan to handle them when they do arise.
PRIVO uses HTTP response status codes to indicate the success or failure of Partner API requests. When a request fails, the Platform returns an error using the appropriate status code. In general, there are 4 status code ranges you can expect.
All methods should return valid HTTP status codes.
-
200 - Ok - Everything worked as expected. Good Job!
-
201 - Created - Methods that create records should return this code on success.
-
208 - Already Reported - For example, this will show when a parent registers a second child.
-
302 - Found - URL redirection was successful.
-
400 - Bad Request - Returned if the incoming data parsing failed, or if the JSON was semantically invalid.
-
401 - Unauthorized - Request needs proper authorization.
-
403 - Forbidden - Provided authorization is insufficient.
-
404 - Not Found - The requested resource doesn't exist.
-
429 - Too Many Requests - Too many requests hit the API too quickly. We recommend an exponential backoff of your requests.
-
5xx - Server Errors - Something went wrong on PRIVO's end (these are very rare).
In addition, all responses will have a standard wrapper that indicates an more detailed information on the operation.
Expired Token Error Response:
{
"error": "invalid_token",
"error_description": "Invalid access token: eyJhbGciOiJSUzIi.......3gZthv7Y"
}
Response Wrapper :
{
"message": "Ok", //detailed status message
"status": "success", //success or fail
"entity": [
/* the actual data returned by the operation; may be scalar or array, depending on the method */
],
"validationErrors": [
/* any errors on the inputs */
],
"resultCount": 0, /* if entity is a collection, number of objects in the collection */
"totalCount": 0/* total number of objects matching the request, if entity is
a collection, in order to support paging */
}
Error Handling
A Partner integration might have to deal with errors from time to time. The right approach and idempotency semantics to use for handling errors depend on the type of error being handled. Idempotency is defined as being able to apply the same operation multiple times without changing the result beyond the first try. Because a certain amount of intermittent failure is to be expected, Clients need a way of reconciling failed requests with a server and idempotency provides a mechanism for that.
The Platform API guarantees the idempotency of 'GET' and 'DELETE' requests, so it’s always safe to retry them when an error has been detected.
Content Errors
Content errors are the result of the contents of an API request being invalid and return a 4xx error code. Integrations should correct the original request, and try again.
Network Errors
Network errors are the result of connectivity problems between Client and Server and tend to manifest as low-level errors like socket or timeout exceptions. When intermittent problems occur, Clients are usually left in a state where they don't know or are not sure whether or not the server received the request. These events can be handled in a number of ways. For GET' or 'DELETE' requests it is safe to simply retry the request (e.g. you cannot delete the same User twice). For some 'POST' requests it is also safe to retry the request as the nature of the request itself will block duplicated events. For example, when registering a minor or adult with a unique email address because the PRIVO system does not allow duplicated email address's to be used accross multiple accounts. This same scenario may not necessarily hold true for child registrations. In some cases it will, like when a unique identifier is included in the request ( e.g. a unique Username). In other cases though, it may not. Partners can work with their PRIVO representative to determine what types of errors are possible in the integration to come up with a error handling plan that suites the integration.
Server Errors
Server errors are the result of a server-side problem and return a 5xx error code. These errors are the most difficult to handle, so we try to ensure that they happen as infrequently as possible. The result of a 5xx request should be treated as indeterminate. The most likely time to observe one is during a production incident and generally during such an incident's remediation. PRIVO engineers examine failed requests and try to appropriately reconcile the results of any mutations that resulted in server errors.
It is also possible for server errors to occur on the Client-side of the integration as well, for example during a webhook response. PRIVO attempts to identify all Client-side server errors using various types of system monitoring that has been put in place. When detected, PRIVO will attempt the request up to 3 times over the course of the proceeding minute. If the error does not resolve itself, then the monitoring system delivers a report of the error to the PRIVO engineering team. Such reports can be shared with Partner in an attempt to make aware the resulting issue and help Partner to remediate the problem.
Contact PRIVO:
For error handling help, contact a PRIVO representative by sending an email to the PRIVO Support Team at support@privo.com.
Invoke Registration Widget
The PRIVO Registration Widget provides registration functionality for new users utilizing the PRIVO UI layout. The request properties are used as follows:
Property | Required? | Description |
---|---|---|
siteId | REQUIRED | The site/service that the User is registering for. Provided by PRIVO administrator. |
mode | OPTIONAL | Set to SIMPLE if rendering widget in an iframe, otherwise, do not include in request. SIMPLE mode removes the PRIVO UI header and footer values. |
siteToken | OPTIONAL | Provide this parameter if the desire is to have user redirected to PRIVO Account Settings upon completion of registration. Provided by PRIVO administrator. |
redirect_uri | REQUIRED | The Client endpoint URL to redirect the User to upon completion of registration. |
access_token | REQUIRED | RP client access_token requested through /oauth/token endpoint. |
NOTE: If mode=SIMPLE is being utilized the redirecting of the user upon successful authentication must be handled on the partner's website. One technique is to, on the redirected partner page, add a JavaScript onload handler to process the request from PRIVO and redirect the user accordingly.
The fragment part of the redirect_uri contains the following properties.
Property | Description |
---|---|
serviceId | Unique identifier for the User and is the same as standard claim ‘sub’. |
access_token | The User access token to make subsequent API calls, such as /userinfo. |
Sample Request:
GET {{url}}/lgs?
siteId=999
&mode=SIMPLE
&redirect_uri=https://client.example.org/cb
&access_token=eyJhbGciOiJSUzIi.......3gZthv7Y
Sample Response:
HTTP/1.1 302 Found
Location: https://client.example.org/cb?
serviceId=2f35732f467......a6735424c6e4f413d3d
&access_token=MMJGKKssey......iOiJyc2Ex
Using the Identity Verification JS SDK
This guide provides information needed to start development using the PRIVO JavaScript Verification API. Client ID and secret are required in order to utilize PRIVO’s APIs.
An Asynchronous Programming Model
This SDK uses an asynchronous programming model in which operations are triggered and then run in the background until they are completed. Upon successful or unsuccessful completion, the operation invokes a callback/webhook function, which is to be provided by the developer, and returns Verification Response objects that include the results of the given operation.
The callback/webhook function should handle the response in an appropriate manner, as determined by the application. Production callback/webhooks must be delivered to secure endpoints. For more information about Callbacks / Webhooks, click here. Polling can be used to actively retrieve timely responses as an alternative or along-side callback/webhook function. For more information on Polling, click here.Making Calls
The following script tag must be added to the head section of the partner page.
NOTE: Client credentials and/or API Keys are required and must be supplied by a PRIVO Administrator.
Development:
https://verification-int.privo.com/vw/privo.min.js
Production:
https://verification.privo.com/vw/privo.min.js
Verification Request
This section details the verification request. To invoke the verification widget, the verification method in the PRIVO SDK has the signature:
The profile object may contain multiple members consisting of information about the user to be verified and when passed must be included in the body of the request.
The verify_config object consists of members detailing the configuration of the verification widget and must be included in the body of the request.
Signature:
privo.verify.showVerify(profile, verify_config);
Property | Required | Type | Description |
---|---|---|---|
apiKey | No | string | Provided by PRIVO Admin. |
siteIdentifier | Yes | string | Provided by PRIVO Admin. |
displayMode | Yes | string | Possible values: popup, redirect |
Sample request:
function showPrivoModule() {
var verify_config = {
apiKey: '(apikey)',
siteIdentifier: '(siteIdentifier)',
displayMode: 'redirect',
}
var profile = {
partnerDefinedUniqueID: '(some_identifier)'
}
privo.verify.showVerify(profile, verify_config);
}
Profile Members
Property | Required | Type | Description |
---|---|---|---|
firstName | No | string | 50 character max |
lastName | No | string | 50 character max |
birthDate (DEPRECATED) | No | long | Number of milliseconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time |
birthDateYYYYMMDD | No | string | Birth Date in "yyyy-mm-dd" format |
No | string | Email address of the user who is verifying. 256 character max | |
postalCode | No | string | If US, numeric-5 postal code |
phone | No | string | In the full international format (e.g. "+17024181234") |
partnerDefinedUniqueID | No | string | Unique identifier passed by Patner and returned in all responses by PRIVO. |
Sample request body:
{
"firstName": "Mickey",
"lastName": "Moose",
"birthDate": 18000000,
"email": "jdoe@someemail.com",
"partnerDefinedUniqueID": "(some_identifier)",
}
Verification Response
The Verification Response is an object used by PRIVO to pass return values of API methods.
Upon successful or unsuccessful completion, the operation invokes a callback/webhook endpoint function, which is provided by the partner developer.
The PRIVO service expects the callback/webhook function to have the following standard signature.
The one parameter that the callback/webhook function receives is the response object which contains the values returned from the API method.
Field | Type | Description |
---|---|---|
event | string | The event for which the callback/webhook pertains to. See Verification Events for possible values. |
errorCode | integer | The result code of the operation when an error occurs. See Error Codes section. |
errorMessage | string | A short textual description of an error associated with the errorCode for logging purposes. See Error Codes section. |
result | object | The resulting object for the given event. For the structure of this object, see reference section for each API call. Value may be undefined. |
PartnerCallbackMethod(response)
Verification Events
Event | Description |
---|---|
onVerifyCancel | When user has canceled the verification. |
onVerifyComplete | When user has successfully completed the verification process and has been verified. |
onVerifyDone | When the user has completed the verification and closed the verification widget. |
onVerifyError | If an error occurs. See Error Codes section. |
Sample code usage:
privo.verify.addEventHandlers({
onVerify: handleVerifyEvent,
onVerifyDone: handleDoneEvent,
onVerifyCancel: handleCancelEvent,
onVerifyError: handleErrorEvent
});
onVerifyCancel
The user has canceled the verification process and the widget has closed.
onVerifyComplete
User has successfully completed the verification process (Pass or Fail). Details about the verification result are passed in the result.verificationResponse member of the Response object.
result.verificationResponse member:
Property | Type | Description |
---|---|---|
verified | boolean | Has the user successfully verified? |
transactionID | string | Unique identifier for the transaction. Partner can retain this value for traceability. |
verificationMethodName | string | The verification method chosen by the user. Possible values: CreditCard, DriversLicense, SSN, CorporateEmail, PrintForm, Phone |
matchOutcomeName | string | Specific outcome for the verification request. Possible values: Pass, Pending (when the user has chosen an offline method of verification, such as Phone or PrintForm, matchOutcome will be ‘Pending’). |
matchCode | string | A code that identifies the field groups that are matched in the verification request. May be undefined. |
matchValues | object | Key/value map that contains matched fields and values (for CC or SSN verification). May be undefined. |
requestID | string | Unique identifier for the verification request. The Partner should retain this value for traceability. |
redirectURL | boolean | Return URL address passed by partner to send the user directly following onVerifyDone event. May be null. |
partnerDefinedUniqueID | string | Value passed by Partner in verify_config of the verification request. PRIVO returns this value in the onVerifyComplete event. Can be undefined. |
requestTimestamp | long | Timestamp of the completed verification request. |
locale | string | Location of the user as defined by their browser settings. |
identificationNumber | string | Unique number provided to user when an offline verification method is chosen. This value can be used by Partner and PRIVO to identify the given pending request. |
attemptId | string | Identifier used to notate the attempt request. |
message | string | For debug reasons - if error occurs error message will be provided here. May be null. |
Sample response:
{
"event": "verify-complete"
"result": {
"verificationResponse": {
"verified": true,
"verificationMethod": 5,
"matchOutcome": 1,
"redirectUrl": "",
"message": "",
"errors": [],
"requestTimestamp": 1633679265147,
"locale": "en-GB",
"identificationNumber": "",
"attemptId": 99208,
"verificationMethodName": "SSN",
"verificationOutcomeName": "Pass",
"transactionID": "71568127R55890",
"requestID": "331724",
"partnerDefinedUniqueID": "OTA3Mi49MTQ3NjE1NTg2OTM="
}
}
}
Sample code usage:
function verifySuccessCallback(response) {
if (!response.errorCode) {
//Handle verify success
alert('Awesome, you are verified!');
} else {
// Handle error
alert('Darn, something went wrong!'+'\n' +
'Error details: ' + response.errorMessage + '\n' +
'Error code: ' + response.errorCode + '\n' +
'In event: ' + response.event;
}
}
onVerifyDone
User has successfully verified and closed the verification widget. Details about actions the user has taken after verifying are passed in the data member of the response object.
Currently, the single action a user may take is to create a PRIVO account. If the user has opted to save their verification status by adding a password, then a PRIVO serviceId will be included in the response. If not, the serviceId member will be null.
Property | Type | Description |
---|---|---|
VERIFY_ACCOUNT | string | When user created PRIVO iD account by adding a password after submitting a verification request. Email address provided for verification is used to create the account. Event includes serviceId of the created user in the PRIVO database. |
{
"result": {
"serviceId": "43536841536865248985654343644322352341165325596"
"verificationResponse": {
...
}
}
}
onVerifyError
Verification failed and the verification widget is closed. Control returns user to the partner site. The partner should act upon the error accordingly.
Error Codes:
Error Code | Error Message |
---|---|
10001 | Invalid API Key or access_token |
10002 | Missing site_id parameter |
10003 | Unexpected error |
10100 | Invalid email address |
10101 | Misconfigured verification methods |
412 | Too many attempts |
Sample body response:
{
"error": "invalid_token",
"error_description": "Invalid access token: eyJraWQiOiJyc2ExIiwiYWxnIjoi...jNj87T9y_IfxafI4a-BI"
}
Verification Webhooks
In addition to webhooks that fire for onVerifyComplete and onVerifyDone events, partners can listen for additional offline webhooks from the PRIVO system for the status of offline verification responses. Webhooks are setup by a PRIVO Administrator at the partner config level.
For additional security, verification webhook responses will return the partner provided API Key back to partner in the webhook response header.
There are multiple types of webhooks that can be utilized. One such method (recommended) is to use the Key/Value Pair. Partner can define a unique API key for their secure endpoint. For more detailed information, see Callbacks / Webhooks.
Property | Type | Description |
---|---|---|
VERIFY_OFFLINE_VERIFIED | string | When PRIVO Administrator approves an offline verification request. |
VERIFY_REMOVED | string | When PRIVO Administrator declines an offline verification request. |
VERIFY_PURGED | string | When verification request was purged according to partner defined expiration policy. Default epiration time is - 30 days. |
Sample KEY / VALUE response header:
{
"timestamp":1557934559462,
"url":"/echo/v0.1/callback/90001-webhook/",
"method":"HEAD",
"headers":{
"User-Agent":"Apache-HttpClient/4.5.4 (Java/1.8.0_212)",
"(webhook_name)":"(webhook_apiKey)",
"Accept":"text/plain, application/json, application/*+json, */*",
"Connection":"Keep-Alive",
"Host":"example.privo.com",
"content-length":"0",
"Content-Type":"text/plain"
},
"content":""
}
Sample webhook response:
{
"webhookId": 1234,
"event": "VERIFY_VERIFIED"
"data": {
"verified": true,
"transactionID": "45914"
"verificationMethod": "SSN",
"matchOutcome": "Pass",
"matchCode": "6",
"message": null,
"requestID": "35459",
"redirectUrl": null,
"partnerDefinedUniqueID": "(some_identifier)",
"requestTimestamp": 145245796521,
"locale": "en_US",
"identificationNumber": null,
"attemptId": 99247
}
Polling
Partners can poll the PRIVO system for verification responses. Polling can be done by doing a GET
call to
the requestId
endpoint . Partner authentication with access token is required to make this API call. The response will
contain verification results related to the given request id.
NOTE: There may be multiple results returned if the user attempted multiple verification methods
Sample request:
GET: https://privohub-int.privo.com/api/verification/(requestId)
HEADERS:
Content Type: application/json
Authorization: (access_token)
Sample response:
{
"validationErrors": [],
"status": "success",
"message": null,
"resultCount": 1,
"totalCount": 1,
"entity": [
{
"method": "Phone",
"outcome": "Pending",
"requestDate": "1524522158000",
"modified": null,
"requestCount": 1,
"matchCode": null,
"attemptId": 99547
}
],
}
Verification Widget Working Example
We have provided a working example of the verification widget. This example will display a simple form which allows partner to invoke the verification widget and test the integration requirements.
Partner required to obtain access from a PRIVO administrator. If you do not already have access to this working example then you can request access. Send an email to devops@privo.com with your contact information and a representative will be in contact with you to arrange access.
NOTE: Be sure to put Access request for verification widget working example in the subject line.
<html>
<head>
<!-- PRIVO Verification JS -->
<script src="https://verification-int.privo.com/vw/privo.min.js"></script>
<!-- IMPORTANT: Retrieve API_KEY and siteIdentifier from PRIVO Customer Support -->
<script type="text/javascript">
const handleVerifyEvent = (resp) => {
console.log(resp);
};
privo.verify.addEventHandlers({
onVerify: handleVerifyEvent,
onVerifyDone: handleVerifyEvent,
onVerifyCancel: handleVerifyEvent,
onVerifyError: handleVerifyEvent,
});
const verificationConfig = {
apiKey: "", // your api key here
siteIdentifier: "", // your site identifier here
displayMode: "redirect",
};
// reformat string formatted as mm/dd/yyyy (local format)
// to string formatted into "yyyy-MM-dd" (sdk format)
// (you can use any time lib to do this: date-fns, moment, luxon, etc)
const getFormattedDate = (rawDate) => {
const dateParts = rawDate?.split("/");
if (!!dateParts && dateParts.length === 3) {
return `${dateParts[2]}-${dateParts[0]}-${dateParts[1]}`;
}
};
//remove phone separators
const getFormattedPhone = (rawPhone) => rawPhone?.replace(/-/g, "");
const verify = (e) => {
e.preventDefault();
const displayMode = document.querySelector(
'input[name="displayMode"]:checked'
).value;
const partnerDefinedUniqueID = document.querySelector(
'input[name="partnerDefinedUniqueID"]'
).value;
const firstName = document.querySelector(
'input[name="firstName"]'
).value;
const lastName = document.querySelector('input[name="lastName"]').value;
const birthDateYYYYMMDD = getFormattedDate(
document.querySelector('input[name="dob"]').value
);
const postalCode = document.querySelector(
'input[name="postalCode"]'
).value;
const email = document.querySelector('input[name="email"]').value;
const phone = getFormattedPhone(
document.querySelector('input[name="homePhoneNumber"]').value
);
const profile = {
partnerDefinedUniqueID,
firstName,
lastName,
birthDateYYYYMMDD,
postalCode,
email,
phone,
};
privo.verify.showVerify(profile, {
...verificationConfig,
displayMode,
});
};
const applyDefaults = () => {
event.preventDefault();
document.querySelector('input[name="firstName"]').value = "Mickey";
document.querySelector('input[name="lastName"]').value = "Moose";
document.querySelector('input[name="dob"]').value = "01/01/1970";
document.querySelector('input[name="postalCode"]').value = "35485";
document.querySelector('input[name="homePhoneNumber"]').value =
"+17037865555";
document.querySelector('input[name="partnerDefinedUniqueID"]').value =
btoa("" + 10000 * Math.random());
};
const reset = () => {
document.getElementById("verifyForm").reset();
};
window.onload = () => {
document.querySelector("#verify").addEventListener("click", verify);
document
.querySelector("#apply-test-data")
.addEventListener("click", applyDefaults);
document
.querySelector("#reset-test-data")
.addEventListener("click", reset);
};
</script>
<title></title>
</head>
<body>
<div class="center-block" style="width: 300px; text-align: center">
<h4>Verification Request</h4>
<hr />
<form id="verifyForm" name="verifyForm">
<div class="form-group">
<input
type="text"
maxlength="20"
name="firstName"
placeholder="first name"
class="form-control"
/>
</div>
<div class="form-group">
<input
type="text"
maxlength="20"
name="lastName"
placeholder="last name"
class="form-control"
/>
</div>
<div class="form-group">
<input
type="text"
name="dob"
placeholder="birthdate"
class="form-control"
placeholder="mm/dd/yyyy"
/>
</div>
<div class="form-group">
<input
type="email"
maxlength="255"
name="email"
placeholder="email"
class="form-control"
/>
</div>
<div class="form-group">
<input
type="text"
maxlength="9"
name="postalCode"
placeholder="postal code"
class="form-control"
/>
</div>
<div class="form-group">
<input
maxlength="9"
name="homePhoneNumber"
placeholder="phone number"
class="form-control"
/>
</div>
<div class="form-group">
<input
name="partnerDefinedUniqueID"
placeholder="partnerDefinedUniqueID"
class="form-control"
/>
</div>
<div class="form-group">
<label class="radio-inline">
<input
type="radio"
name="displayMode"
id="displayMode1"
value="redirect"
checked
/>
redirect
</label>
<label class="radio-inline">
<input
type="radio"
name="displayMode"
id="displayMode2"
value="popup"
/>
popup
</label>
</div>
<br />
<div class="btn-toolbar">
<button type="button" id="verify" class="btn btn-primary btn-wide">
Verify
</button>
<button type="button" id="apply-test-data" class="btn">
Apply Test Data
</button>
<button type="button" id="reset-test-data" class="btn">Reset</button>
</div>
</form>
</div>
</body>
</html>
Working example endpoint:
https://privohub-int.privo.com/
https://verification-dev.privo.com/vw/#/
Identity Verification: Direct URL Integration
This guide provides information needed to use PRIVO Identity Verification with the direct URL Integration.
Diagram of Integration
Identity Verification URLs:
Integration Environment:
https://verification-int.privo.com/vw/index.html#/intro
Production Environment:
https://verification.privo.com/vw/index.html#/intro
Identity Verification URL Query Parameters:
Property | Required | Description |
---|---|---|
referer | Yes | Provided by PRIVO Admin. |
service_identifier | Yes | Provided by PRIVO Admin. |
redirect_url | Yes | Partner site URL used by PRIVO Identity Verification to return users after verification |
first_name | No | User first name |
last_name | No | User last name |
birth_date_yyyymmdd | No | user Birth Date in yyyy-MM-dd format |
No | User email address | |
postal_code | No | User Postal Code |
phone | No | User Phone in international format (E.164 standard) |
partner_defined_unique_id | No | User unique identifier in partner system |
NOTE: All Parameters must be URL Encoded
Examples:
https://verification-int.privo.com/vw/index.html?service_identifier=request&referer=request&redirect_url=https%3A%2F%2Fprivo.com&first_name=John&partner_defined_unique_id=12345678#/intro
https://verification.privo.com/vw/index.html?service_identifier=request&referer=request&redirect_url=https%3A%2F%2Fprivo.com&first_name=John&last_mame=Doe$email=john.doe1%40privo.com&partner_defined_unique_id=12345678#/intro
Using the JS Login SDK
This section provides information needed to start development using the PRIVO JavaScript Login SDK. This widget is OpenID Connect compliant and provides the capability for User's to authenticate on a given partner service. Upon successful User authentication that User's session will be associated with the service that is linked to the client_id used to invoke the widget.
An Asynchronous Programming Model
PRIVO uses an asynchronous programming model in which operations are triggered and then run in the background until they are completed. Upon successful or unsuccessful completion, the operation invokes a callback function , which is provided by the developer, and returns a Login Response object that includes the results of the login operation. The callback function should handle the response in an appropriate manner, as determined by the application.
Getting Started
The following javascript block must be added to the head section of the partner page.
NOTE: jQuery 1.10 or higher is required.
JavaScript source environments:
Integration: https://privohub-int.privo.com/
Production: https://privohub.privo.com/
JavaScript block:
<script id="privoLoginJS" type="text/javascript">
var js = document.createElement("script");
js.src = 'https://privohub-int.privo.com/client/privo-login.js';
js.onload = function () {
/*
PRIVO Login configuration object
*/
privoLoginConfig = {
loginDisplay: [ PRV.DisplayType.NORMAL | PRV.DisplayType.SIMPLE | PRV.DisplayType.TEMPLATE ],
siteIdentifier: '[ Partner siteId ]',
loginPageName: '[ When loginDisplay = PRV.DisplayType.TEMPLATE this is the name of the login template ]',
state: '[ Opaque value used by the partner to maintain state between the logout request and the callback to the endpoint ]',
clientId: '[ OAuth client_id ]',
scopes: [ Requested user scopes, ex: ["openid", "profile", "user_profile", "additional_info"] ],
prompt: [ Requested login prompt, ex: ["none", "login", "consent", "select_account"] ],
width: [ Recommended width of login window, ex: 400],
height: [ Recommended height of login window, ex: 375]
};
/*
Register event handlers
*/
PRV.login.addEventHandlers({
onLogin:handleLoginEvent,
onLoginError:handleLoginEvent,
onClose:handleClosePopupEvent
});
};
document.getElementsByTagName("head")[0].appendChild(js);
</script>
Login
This sections details the login request. To invoke the login widget, perform the following:
JavaScript request:
PRV.login.showLogin(privoLoginConfig);
Login Response:
The Login Response is an object used by PRIVO to pass results of a user authentication flow.
Upon successful or unsuccessful completion, the operation invokes a callback function, which is provided by the partner developer.
The PRIVO service expects the callback function to have the following standard signature.
The one parameter that the callback function receives is the response object which contains the values returned from the API method.
Response Object:
Field | Type | Description |
---|---|---|
event | string | The event for which the callback pertains to. See Login Events for possible values. |
data | object | The resulting object for the given event. For the structure of this object, see reference section for each API call. May be null. |
Signature:
aPartnerCallbackMethod(response)
Login Events
There are only two states of login events: Pass and Fail.
Event | Description |
---|---|
onLogin | When user has successfully completed the login process. |
onLoginError | If an error occurs. |
onClose | When popup window is closed. It doesn’t depend on login outcome and will be invoked at the end of successful and error result. |
onLogin
User has successfully completed the login process. Details about the login result are passed in
the data.loginResponse
member of the Response object.
Data Members:
Property | Type | Description |
---|---|---|
jwt | object | Result of the user authentication represented as a JSON Web Token (JWT) |
userinfo | object | Userinfo for the authenticated user |
startPlayMode | boolean | Applicable to minor authentications that were successful, however, the minor user has not yet received parent consent (value of true). |
Sample response:
{
"jwt": {
"access_token": "eyJraWQiOiJyc2ExIiwiYWxnI...s_Epf8ux2B40",
"token_type": "Bearer",
"state": "SOMELOCALSTATE",
"expires_in": "86399999",
"id_token": "eyJr...XM5iotrg"
},
"startPlayMode": false,
"userinfo": {
"sub": "386a354e4d364364674f4e2f562b4930617477564f773d3d",
"name": "Bob Rose",
"given_name": "Bob",
"family_name": "Rose",
"picture": "https://somelocation.jpg",
"zoneinfo": "America/New_York",
"locale": "en_US",
"updated_at": "1501503836",
"role_identifier": "STANDARD_PARENT_1",
"minor": false,
"teen": false,
"teacher": false,
"student": false,
"approved": true,
"shadow_account": false,
"site_token": "495059377358756a7362486b687a52485462415775673d3d",
"permissions": [
{
"on": true,
"consent_time": 1501300800,
"request_time": 1501361767,
"feature_active": true,
"feature_id": 1531,
"feature_identifier": "PRIVO-101",
"feature_category": "Standard",
"feature_name": "Tier A_0 - Starter Account",
"attributes": [
{
"id": 118305,
"value": "blue",
"attributeName": "favColor"
}
]
}
],
"activation_time": 1501364746,
"verification_tier": "G",
"user_profile": {
"role_identifier": "STANDARD_PARENT_1",
"site_token": "495059377358756a7362486b687a52485462415775673d3d",
"minor": false,
"teen": false,
"teacher": false,
"student": false,
"approved": true,
"shadow_account": false,
"activation_time": 1501364746,
"verification_tier": "G",
"attributes": [
{
"id": 118305,
"value": "blue",
"attributeName": "favColor"
},
{
"id": 118304,
"value": "1",
"attributeName": "favorite.cars"
}
],
"display_names": []
}
}
}
onLoginError
Login failed and the login widget is closed. Control returns user to the partner site. The partner should act upon the error accordingly.
Sample response:
{
"error": {
"error": "invalid_scope",
"error_description": "Invalid scope: SOME_INVALID_SCOPE"
}
}
Initialize SDK
privo.ageGate.init(config)
interface Configuration {
serviceIdentifier: string;
displayMode: AgeGateDisplayMode;
}
type AgeGateDisplayMode = "popup" | "redirect";
The method initiates AgeGate SDK.
Redirect mode implies sending users to a different URL from the one they originally requested. Popup mode implies
displaying via a popup browser window that is displayed on top of the existing windows on the screen.
These modes will take place only in case Privo needs to collect some data from the user (for example birth date). Details
Age Gate init entry parameters:
serviceIdentifier - service identifier
displayMode - widget display mode configurations: "popup","redirect"
JS (Typescript) Age Gate SDK location (use it as a script src for SDK uploading):
Development Environment:
https://age-int.privo.com/gate/privo.min.js
Development Environment (Typescript types):
https://age-int.privo.com/gate/privo.d.ts
Production Environment:
https://age.privo.com/gate/privo.min.js
Production Environment (Typescript types):
https://age.privo.com/gate/privo.d.ts
After initialization you can set up onStatusChange callback. See doc
Example:
<!DOCTYPE html>
<html>
<head>
<title>Age Gate load Demo</title>
<script src="https://age.privo.com/gate/privo.min.js"></script>
<script>
window.onload = async () => {
privo.ageGate.init({
serviceIdentifier: "your_identifier",
displayMode: "redirect"
});
const response = await privo.ageGate.getStatus("user-identifier");
console.log(response)
}
</script>
</head>
<body>
</body>
</html>
Check Status
await privo.ageGate.getStatus(userIdentifier)
The method allows checking the existing Age Gate status.
Age Gate Status entry parameters (inside Promise):
userIdentifier - optional string field, external user identifier
Age Gate Status response:
interface AgeEvent {
status: AgeCheckStatus;
userIdentifier?: string;
agId?: string;
ageRange?: AgeRange;
}
type AgeCheckStatus =
"Undefined" |
"Pending" |
"Allowed" |
"Blocked" |
"MultiUserBlocked" |
"AgeEstimationBlock" |
"ConsentRequired" |
"ConsentApproved" |
"ConsentDenied" |
"IdentityVerificationRequired" |
"IdentityVerified" |
"AgeVerificationRequired" |
"AgeVerified" |
"AgeBlocked" |
"Canceled";
interface AgeRange {
start: number;
end: number;
jurisdiction?: string;
}
status - "Undefined", "Blocked", "MultiUserBlocked", "AgeEstimationBlock", "Allowed", "Сanceled", "Pending", "ConsentRequired", "ConsentApproved", "
ConsentDenied, "AgeVerificationRequired", "AgeVerified", "AgeBlocked", "IdentityVerificationRequired", "
IdentityVerified".
userIdentifier - string field, external user identifier
agId - string field, age gate identifier
Please check the Age Gate Status Description here
Run
privo.ageGate.run(data)
interface CheckAgeData {
userIdentifier?: string;
birthDateYYYYMMDD?: string;
birthDateYYYYMM?: string;
birthDateYYYY?: string;
age?: number;
countryCode?: string;
}
The method runs the Age Gate check: if the birth date is passed or filled in by a user, the method will return the
status "Undefined", "Blocked", "MultiUserBlocked", "AgeEstimationBlock", "Allowed", "Сanceled", "Pending", "ConsentRequired", "ConsentApproved", "ConsentDenied, "
AgeVerificationRequired", "AgeVerified", "AgeBlocked", "IdentityVerificationRequired", "IdentityVerified", depending on
the user’s age and set by a partner configuration parameters.
If the birth date is not passed, a user will be navigated to a corresponding entry window and forced to fill in the
birthDate field. The new entry window has two display modes: Redirect and Popup. Display Mode Details
CheckAgeData entry parameters:
userIdentifier - optional field, external user identifier
birthDateYYYYMMDD - optional field, external user birth date in "yyyy-MM-dd" format
birthDateYYYYMM - optional field, external user birth date in "yyyy-MM" format
birthDateYYYY - optional field, external user birth date in "yyyy" format. Derived birthDate will be calculated with Dec 31 by default
age - optional field, external user age format. Derived birthDate will be calculated with current day and month by default
countryCode - optional field, two-letter country code (ISO 3166-1
alpha-2 Wiki).
Result of invocation privo.ageGate.run(data) will be provided in onStatusChange callback function.
privo.ageGate.onStatusChange(event => {}) should be set before privo.ageGate.run is invoked to get results. doc
Please check the Age Gate Status Description here
Sample SDK Usage Example:
ageGate.init({
serviceIdentifier,
displayMode: "redirect",
});
ageGate.onStatusChange((event) => {
setEvents((prev) => [...prev, event.status]);
});
// ...
ageGate.getStatus(state.userIdentifier).then((event) => {
setEvents((prev) => [...prev, event.status]);
});
// ...
ageGate.run({
userIdentifier,
countryCode,
birthDateYYYYMMDD,
});
// ...
ageGate.recheck({
userIdentifier,
countryCode,
});
onStatusChange (Response callback)
privo.ageGate.onStatusChange(event => {})
The method is invoked by the Run/Recheck methods to handle status change results after Run/Recheck.
It returns:
interface AgeEvent {
status: AgeCheckStatus;
userIdentifier?: string;
agId?: string;
ageRange?: AgeRange;
}
interface AgeRange {
start: number;
end: number;
jurisdiction?: string;
}
Example of real data:
{
"status":"Allowed",
"userIdentifier":"9ede0f0-...a78",
"agId":"861dc238-...-c1dfe",
"ageRange":{"start":18, "end":120, "jurisdiction": "coppa_us"}
}
Age Event parameters:
status - "Undefined", "Blocked", "MultiUserBlocked", "AgeEstimationBlock", "Allowed", "Сanceled", "Pending", "ConsentRequired", "ConsentApproved", "
ConsentDenied, "AgeVerificationRequired", "AgeVerified", "AgeBlocked", "IdentityVerificationRequired", "
IdentityVerified"
userIdentifier - optional field, external user identifier
agId - optional field, age gate identifier
Please check the Age Gate Status Description here
Age Recheck
privo.ageGate.recheck(data)
interface CheckAgeData {
userIdentifier?: string;
birthDateYYYYMMDD?: string;
birthDateYYYYMM?: string;
birthDateYYYY?: string;
age?: number;
countryCode?: string;
}
The method allows rechecking data if the birth date provided by a user was updated.
privo.ageGate.onStatusChange(event => {}) should be set before privo.ageGate.recheck is invoked to get results. doc
Age Gate Recheck entry parameters:
userIdentifier - optional field, external user identifier
birthDateYYYYMMDD - optional field, external user birth date in "yyyy-MM-dd" format
birthDateYYYYMM - optional field, external user birth date in "yyyy-MM" format
birthDateYYYY - optional field, external user birth date in "yyyy" format. Derived birthDate will be calculated with Dec 31 by default
age - optional field, external user age format. Derived birthDate will be calculated with current day and month by default
countryCode - optional field, two-letter country code (ISO 3166-1 alpha-2 Wiki).
Age Gate Show Identifier Modal
privo.ageGate.showIdentifierModal(userIdentifier)
The method will show a modal dialog with user age gate identifier (can be used to contact customer support)
Age Gate Show Identifier Modal parameters:
userIdentifier - optional string field, external user identifier
Hide
privo.ageGate.hide()
The method allows a partner to hide the Age Gate widget.
Age Gate Status Description
Status | Description |
---|---|
Undefined | The status is returned if it is an initial run |
Blocked | Depending on partner configuration rules, a user is blocked (for example, if a child is too young to be redirected to the Consent or Age Verification flows) |
MultiUserBlocked | User is blocked according to multi-user activity(can appear only for multi-user integrations) |
AgeEstimationBlock | User is blocked according to age estimation step results(can appear only if age estimation is enabled) |
Allowed | The status is returned if a user age doesn’t require any verification, depending on partner configuration rules |
ConsentRequired | The status is returned in case a user hasn’t started the Consent flow. Once the Age Gate Service is run, a user will be forced to go through the Consent flow, in case child's age data falls within a particular data range, which is set by partner configuration rules |
ConsentApproved | A user successfully passed the Consent flow (online or offline passed) |
ConsentDenied | A user failed passing the Consent flow |
AgeVerificationRequired | The status is returned in case a user hasn’t started the Age Verification flow. Once the Age Gate Service is run, a user will be forced to go through the Age Verification flow, in case child's age data falls within a particular data range, which is set by partner configuration rules |
AgeVerified | A user successfully passed the Age Verification flow |
AgeBlocked | A user is blocked while passing the Age Verification flow because partner configuration is not satisfied |
IdentityVerificationRequired | The status is returned in case a user hasn’t started the Identity Verification flow. Once the Age Gate Service is run, a user will be forced to go through the Identity Verification flow, in case child's age data falls within a particular data range, which is set by partner configuration rules |
IdentityVerified | A user successfully passed the Identity Verification flow (online or offline passed) |
Pending | The Consent flow/Age Verification/Identity Verification flow was started by a user, but hasn’t been completed (offline waiting). If offline Identity Verification fails, IdentityVerificationRequired status will be returned |
Age Gate Flow Diagrams
Simple Age Gate Flow Diagram
Age Gate Flow Diagram (with Age Recheck)
Initialize SDK
privo.ageVerification.init(config)
interface Configuration {
serviceIdentifier: string;
displayMode: AgeGateDisplayMode;
}
type AgeGateDisplayMode = "popup" | "redirect";
The method initiates AgeVerification SDK.
Age Verification init entry parameters:
serviceIdentifier - service identifier
displayMode - widget display mode configurations: "popup","redirect"
Display mode defines the way of how the widget will be open when the privo.ageVerification.run method is called. Redirect mode implies sending users to a different URL from the one they originally requested. Popup mode implies displaying via a popup browser window that is displayed on top of the existing windows on the screen.
JS (Typescript) Age Gate SDK location:
Development Environment:
https://age-int.privo.com/verification/privo.min.js
Development Environment (Typescript types):
https://age-int.privo.com/verification/privo.d.ts
Production Environment:
https://age.privo.com/verification/privo.min.js
Production Environment (Typescript types):
https://age.privo.com/verification/privo.d.ts
Check Status
privo.ageVerification.getStatus(userIdentifier)
The method allows checking the existing Age Verification status.
Age Verification Status entry parameters:
userIdentifier - optional string field, external user identifier
Age Verification Status response:
export interface AgeVerificationEvent {
status: AgeVerificationStatus;
profile?: AgeVerificationProfile;
}
export type AgeVerificationStatus = "Undefined" | "Pending" | "Confirmed" | "Declined" | "Canceled";
export interface AgeVerificationProfile {
userIdentifier?: string;
firstName?: string;
email?: string;
birthDateYYYYMMDD?: string; // "yyyy-MM-dd" format
phoneNumber?: string; // in the full international format (E.164, e.g. “+17024181234”)
}
status - "Undefined" , "Pending" , "Confirmed" , "Declined" , "Canceled";
profile - child profile verified by PRIVO:
userIdentifier - optional field, external user identifier
firstName - optional field, child user first name
email - optional field, child user email address
birthDateYYYYMMDD - optional field, child user birth date in “yyyy-MM-dd” format
phoneNumber - optional field, child user phone number in the full international format (E.164, e.g. "+17024181234")
Run
privo.ageVerification.run(profile, onChange)
The method runs the Age Verification.
The Run method entry parameters:
profile - child profile field, the parameter is passed from a partner (optional)
onChange - callback that fiers when the verification status is changed. It initiates AgeVerificationEvent
The Run method response:
export interface AgeVerificationEvent {
status: AgeVerificationStatus;
profile?: AgeVerificationProfile; }
The new entry window has two display modes: Redirect and Popup (according to configuration in the
privo.ageVerification.init method).
Redirect mode - implies sending users to a different URL from the one they originally
requested.
Popup mode - implies displaying via a popup browser window that is displayed on top of the existing windows on
the screen.
Please check the Age Verification Status Description table
Hide
privo.ageVerification.hide()
The method allows a partner to hide the Age Verification widget.
JS (Typescript) Age Verification SDK example:
ageVerification.init({
serviceIdentifier,
displayMode: "redirect",
});
// ...
ageVerification.getStatus(userIdentifier).then((event) => {
setEvents((prev) => [...prev, event.status]);
});
// ...
ageVerification.run({firstName, email}, (event) => {
setEvents((prev) => [...prev, event.status]);
})
Age Verification Status Description
Status | Description |
---|---|
Undefined | The status is returned if it is an initial run |
Pending | The Age Verification/Identity Verification was started by a user, but hasn’t been completed (offline waiting); a child user waits for approval from a parent user |
Confirmed | The status is returned if a parent user verifies age for a child user |
Declined | The status is returned if a child user is declined by a parent user to access a partner app |
Canceled | The status is returned in case a user closes down the Age Verification widget until next time they need to use it |
Initialize Age Estimation SDK
Installation via NPM:
npm install privo-age-estimation-sdk
Installation via CDN:
Integration Environment:
https://age-int.privo.com/estimation/privo.min.js
Development Environment (Typescript types):
https://age-int.privo.com/estimation/privo.d.ts
Production Environment:
https://age.privo.com/estimation/privo.min.js
Production Environment (Typescript types):
https://age.privo.com/estimation/privo.d.ts
Example:
<script src="https://age.privo.com/estimation/privo.min.js"></script>
Initialization (via NPM):
import { init } from "privo-age-estimation-sdk";
await init(config)
Initialization (via CDN):
privo.ageEstimation.init(config)
interface PartnerConfiguration {
env: "int" | "prod";
serviceIdentifier: string;
displayMode?: "iframe" | "popup" | "redirect";
}
Age Estimation init entry parameters:
env - type of the PRIVO environment: int - for integration testing, prod - for real users
serviceIdentifier - service identifier provided by Privo.
displayMode - widget display mode configurations: "iframe", "popup","redirect"
Display mode defines the way of how the widget will be open when the privo.ageEstimation.run method is called.
"redirect" mode sends users to a different URL (PRIVO URL) from the one they originally requested.
"popup" mode sends users to a new popup browser window that is displayed on the top of the existing windows on the screen.
"iframe" mode opens iframe on the top of the existing page on the screen.
Note: Before real usage please send an email request to devops@privo.com to whitelist your site origin in the PRIVO CORS settings.
After initialization you can set up onStatusChange callback. See doc
Example (CDN):
<!DOCTYPE html>
<html>
<head>
<title>Age Gate load Demo</title>
<script src="https://age.privo.com/estimation/privo.min.js"></script>
<script>
window.onload = async () => {
privo.ageEstimation.init({
env: "prod",
serviceIdentifier: "your_identifier",
});
}
</script>
</head>
<body>
</body>
</html>
Example (NPM):
import { init } from "privo-age-estimation-sdk";
await init({
env: "prod",
serviceIdentifier: "your_identifier",
})
Run
for CDN version:
privo.ageEstimation.run(data)
for NPM version:
getInstance().run(data)
export interface CheckAgeData {
userIdentifier?: string;
}
The user will be navigated to a corresponding entry window and forced to pass Age Estimation Flow.
CheckAgeData entry parameters:
userIdentifier - optional field, external user identifier
Result of invocation
privo.ageEstimation.run(data)
will be provided in onStatusChange callback function.
privo.ageEstimation.onStatusChange should be set before privo.ageEstimation.run is invoked to get results. doc
Sample SDK Usage Example:
CDN:
privo.ageEstimation.init({
env: "prod",
serviceIdentifier,
});
privo.ageEstimation.onStatusChange((event) => {
// ...
});
// ...
privo.ageEstimation.run({
userIdentifier,
});
NPM:
const instance = await init({
env: "prod",
serviceIdentifier,
});
instance.onStatusChange((event) => {
// ...
});
// ...
instance.run({
userIdentifier,
});
onStatusChange (Response callback)
for CDN version:
privo.ageEstimation.onStatusChange(event => {})
for NPM version:
getInstance().onStatusChange(event => {})
It returns:
interface AgeEstimationEvent {
status: "completed" | "cancelled";
token?: string;
}
Example of real data:
{
"status": "completed",
"token":"9ede0f0-...a78",
}
The method is invoked by the "Run" methods to handle status change results after "Run".
Parameters:
status - Current status of the age estimation request. Can be "completed" or "cancelled".
token - Age Estimation token that contains encoded age information about the user. Token decoding info - doc
Validate JWT token
Validate Age Estimation Token
The Age Estimation Token is a JWT token that contains the estimated age of the user.
The token is either signed, or signed and encrypted by the PRIVO ID Service. If the JWT is both signed and encrypted, the JSON document will be signed then encrypted, with the result being a Nested JWT, as defined in [RFC7519].
The subject claim (sub
) contains a JSON encoded array with a few successful attempts of the estimated user age.
Example of the Age Estimation Token:
Encoded token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImlkMSJ9.eyJzdWIiOiJbMzksNDAsMzksMzgsMzVdIiwiYXpwIjoicHJpdm9sb2NrIiwiYXVkIjoiQWdlRXN0aW1hdGlvbiIsImV4cCI6MTcxMDg3MDAwMSwiaWF0IjoxNzEwODY5MTAxLCJpc3MiOiJodHRwczovL2lkLXN2Yy1pbnQucHJpdm8uY29tIiwianRpIjoiZmQwOTMyYjktYzI4NS00N2NkLTliYmEtMjkwYmI1M2M5MDI0In0.T6Nat3VVkmP9X_TQ9HX08YQTRVsf_phA_KserAscuzYg25SahEANogwSriIStJ8KCiLkqsErXa77jABB65ydyINtIOJOTBNj-ks_JZsdGl9s75hrzbkZ4MfFy7_U_gL7H0NmMzrJWpUU4cNXI9oKdanZsVwIstHPE-GHc7TnwnxHffmPL7vccL_GcnmSYGWI2PRPK5Lgn7ficQZ3avJKZYEGY-vnrEYlwglEtBOcrfxow2l-8T0Oieuylrmf5LVFjSj7v8HDfEx0iU3B1i9ffZsVtCLpVwFyRgHoSiwbs0wXdtcMD_uOAVXBhMfclNBJV4uDR-_hF4kI7pnsRu0IlQ
Decoded header:
{
"typ": "JWT",
"alg": "RS256",
"kid": "id1"
}
Decoded payload:
{
"sub": "[28,29,29,31,30]", // Estimated age, each number represents a different attempt
"azp": "service_identifier", // Service Identifier provided by PRIVO during integration
"aud": "AgeEstimation", // PRIVO product ID
"exp": 1710837509, // Expiration time
"iat": 1710836609, // Issue time
"iss": "https://id-svc-int.privo.com", // Issuer
"jti": "361140de-b74f-45eb-bc06-c51a209862b2" // JWT ID
}
To verify that the Age Estimation Token is valid, ensure that the following criteria are satisfied:
- The Age Estimation Token is properly signed by the PRIVO ID Service. Use the ID Service's public keys available in JWK format in INT env at
https://id-svc-int.privo.com/.well_known/jwks
to verify the token's signature. Please note that public keys depend on Enviroment (INT/PROD) that you are usign. PROD version -https://id-svc.privo.com/.well_known/jwks
. - The value of the authorized party (
azp
) claim must be equal to your Service Identifier (provided by PRIVO). - The value of the audience claim (
aud
) must be equal to one of the PRIVO product IDs (AgeGate, AgeVerification, AgeEstimation, etc) that you integrate. - The value of the issuer (
iss
) claim must be equal tohttps://id-svc.privo.com
on production enviroment orhttps://id-svc-int.privo.com
on integration enviroment. - The value of the issue at (
iat
) claim is the number of seconds from 1970-01-01T00:00:00Z as measured in UTC, and must be less than the current time. - The value of the expiration time (
exp
) claim is the number of seconds from 1970-01-01T00:00:00Z as measured in UTC, and must be greater than the current time.
We strongly recommend using a well-known JWT library to perform the above verification steps. If you don't use any JWT in your codebase, you can find available libraries for your language at https://jwt.io/libraries.
Age Verification Token validation - Java example (JAVA-JWT)
Please see documentation and more examples at:
pom.xml
...
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.22.1</version>
</dependency>
...
Java code
import java.net.URL;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.concurrent.TimeUnit;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.JwkProviderBuilder;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.RSAKeyProvider;
...
public static final String SERVICE_IDENTIFIER = "...";
public static final String PRIVO_PRODUCT_ID = "AgeEstimation";
// INT environment
public static final String PRIVO_ID_ISSUER = "https://id-svc-int.privo.com";
public static final String PRIVO_ID_JWKS_URL = "https://id-svc-int.privo.com/.well_known/jwks";
// PROD environment
// public static final String PRIVO_ID_ISSUER = "https://id-svc.privo.com";
// public static final String PRIVO_ID_JWKS_URL = "https://id-svc.privo.com/.well_known/jwks";
...
// build key provider from JWKS URL
JwkProvider provider = new JwkProviderBuilder(new URL(PRIVO_ID_JWKS_URL))
// allow 5 cached keys for 1 hour
.cached(5, 1, TimeUnit.HOURS).build();
RSAKeyProvider keyProvider = new RSAKeyProvider() {
@Override
public RSAPublicKey getPublicKeyById(String kid) {
try {
return (RSAPublicKey) provider.get(kid).getPublicKey();
} catch (Exception e) {
// FIXME: handle exception
e.printStackTrace();
return null;
}
}
@Override
public RSAPrivateKey getPrivateKey() {
return null;
}
@Override
public String getPrivateKeyId() {
return null;
}
};
Algorithm algorithm = Algorithm.RSA256(keyProvider);
// build the verifier, it can be reused
JWTVerifier verifier = JWT.require(algorithm)
// The value of the issuer (`iss`) claim must be equal to `https://id-svc.privo.com` on production
// enviroment or `https://id-svc-int.privo.com` on integration enviroment.
.withIssuer(PRIVO_ID_ISSUER)
// The value of the authorized party (`azp`) claim must be equal to your Service Identifier
// (provided by PRIVO).
.withClaim("azp", SERVICE_IDENTIFIER)
// The value of the audience claim (`aud`) must be equal to one of the PRIVO product IDs (AgeGate,
// AgeVerification, AgeEstimation, etc) that you integrate.
.withAudience(PRIVO_PRODUCT_ID)
// build the verifier
.build();
...
// verify the token and get the estimated ages
try {
DecodedJWT decodedJWT = verifier.verify(ageEstimationToken);
String ages = decodedJWT.getSubject();
System.out.println(ages); // [39,40,39,38,35]
} catch (JWTVerificationException ex) {
//FIXME: handle exception
ex.printStackTrace();
}
Account Settings
content_markdown: |- Partners can redirect authenticated users to partner branded PRIVO Account Settings to allow them to manage their profile.
Property | Description |
---|---|
siteToken | Required for a partner branded experience. Provided by PRIVO administrator. |
Sample Request:
{url}/account-settings?siteToken=4f6e53656e78...
About Data Attributes and Features
Attributes are defined as "data points" used to facilitate a given User account or feature, either during creation or while updating an account. PRIVO offers a robust set of Standard attributes for use by Partner during registration activity. Partners also have the opportunity to define Custom attributes as their needs determine. Attributes can be assigned at a User role level as well as at a feature defined level.
Additionally, attributes can be defined as a given function such as chat logs whereas the "data" being provided is actually consumed during the activity being performed.
All attributes must be defined for use in the Platform Partner configurations. Each attribute has an identifier which can be used by Partner to determine data use. Custom attribute identifiers are provided by a PRIVO Administrator once they have been defined in the system.
Features are defined as the "things" a User can do on a given service, website or application. PRIVO offers two types of features, Standard and Optional, both of which are defined in their respective section below. Data attributes can be assigned specifically to a given feature thus requiring collection or use of the attribute at the granular feature level.
Features drive the level of consent needed to facilitate the feature activity. Some features require a much higher level of consent then others do. PRIVO works with Partner to determine what features should be presented and at what level those features must obtain consent in order to be activated.
Like attributes, each feature will have an identifier which can be used by Partner to determine feature access and use. Feature identifiers are provided by a PRIVO Administrator once they have been defined in the system.
Standard Attributes
Standard attributes are those "data points" defined by the PRIVO system for use. These are common data points that most Partners would use, want to consume or require during a given registration for example. Standard attribute configurations reside at the base level within the PRIVO system and are shared between all Partners. A given Partner has the ability to determine how the attribute is presented by renaming the attribute for their use. For example, a Partner may have the need to define "Display Name" as "Screen Name" instead. Standard attribute configurations allow for such a modification without affecting how another Partner would utilize the same given attribute.
Standard Attributes:
Property | Identifier | Description | Restrictions/Format |
---|---|---|---|
First Name | firstName | Represents the User | No spaces, numbers or special characters allowed |
Middle Initial | middleInitial | User's middle initial | No numbers, maximum of 4 characters. Periods allowed |
Last Name | lastName | User's given name | No numbers or special characters allowed |
Last Initial | lastInitial | First initial of User's Last Name | Rarely used. No numbers or special characters, maximum of 1 charcter. |
Birth Date | birthDate_YYYYMMDD | Represents age of the User | Must follow format YYYYMMDD |
Email Address | User's email address | Must be proper email with @ and .xx(x) | |
Parental Email Address | parentEmail | Email address of Parent when registering a child | Must be proper email with @ and .xx(x) |
Gender | gender | Sex of the User | Dependent on API may be enumerated as 1,2 OR given as Male, Female |
Mobile Phone | mobilePhone | User's cell phone number | 7 to 10 numbers, no hyphens |
Home Phone Number | address.homePhoneNumber | User's home phone number | 7 to 10 numbers, no hyphens |
Street Address | address.streetAddress1 | User's street number and name | Open field |
Apartment/Suite | address.streetAddress2 | User's suite or apartment number | Open field |
City | address.city | User's city | Open field |
State/Province | address.stateProvince | User's state or province | 2 letters for US, oopen for INTL. |
Region | address.region | User's region | INTL. only. Open field |
Postal Code | address.postalCode | User's postal code | 5 digits for US, open for INTL. |
Country | address.country | User's country | 2 letters e.g. US |
Display Name | displayName | Name that displays in a service | Unique to the given service. Can be used for login to given service |
Username | userName | Username used for authentication | Unique to the entire system |
Custom Attributes
Custom attributes are defined by Partner during integration. All attributes must be added to Partner configurations within the Platform console. A PRIVO Administrator will work with Partner to define values and identifiers for use to accomodate custom attributes. Below find some examples of what a custom attribute may look like.
Custom Attribute Examples: (But not limited to...)
Property | Identifier | Description | Restrictions/Format |
---|---|---|---|
Favorite Color | favColor | User's favorite color | String for color description |
Device Identifier | deviceId | Identifier representing a given device | String |
Partner UUID | partnerUuid | Unique identifier representing User | String supplied by Partner |
Participation ID | participationId | Unique identifier representing User's participation | String supplied by Partner |
Registration PIN | registrationPin | PIN representing a User in the PRIVO system used to associate multiple adults to a given account | String set by PRIVO |
Shirt Size | userShirtSize | Size of a shirt being provided by Service to User | Drop-down |
Standard Features
Standard Features are features that come with registration. Features that are marked Standard cannot be turned on or off individually by a registering User. Standard features represent "things" that are required by a User to interact with the given service, website or application. Generally, standard features are all-or-nothing meaning, if the User wants to be registered for a given service then they must have approval to use all standard features.
Standard Feature Examples (not limited to...):
* Account Credentials
* Contest Participation/Enrollment
* Policy Agreements
* Media Releases
* 3rd-Party Platform Use to Facilitate a Given Activity
* Required Mailings such as Membership Cards
* Membership Enrollments
* Upload Capabilities
Optional Features
Unlike Standard features, Optional features are just that... optional. The User has the option to either accept or decline participation with these features. Sometimes a feature is required to be optional based on governing policy. Other times a Partner service may want to allow a particular "thing" to be optional.
Optional features have the ability to be defined as "Optional by default". Meaning, an optional feature can be presented to the User as "on" or "off" by default. When a feature is presented on, then the User only needs to approve the feature for use and provide any additional data required by said feature. When the feature is presented as off, then the User must turn on the feature prior to approving its use or adding additional data.
Optional Feature Examples (not limited to...):
* Email Newsletters
* Birthday Clubs
* Push Notifications
* Optional Mailings such as Product Magazines
* Leaderboard Display
* Additional Communications
* Sweepstakes Entries
* Device Usage Consent
* 3rd-Party Sharing
REST APIs
These RESTful APIs accept/return JSON formatted data. Sample request/responses below are represented in JSON format. See API Conventions for further information.
All PRIVO Platform resources with the /api/... URI prefix are OAuth 2.0 protected and require authentication for access.
Common API Configuration Members for Use
Property | Type | Description |
---|---|---|
sendParnetEmail | Boolean | Does system send consent email to parent email address provided? |
sendRegistrationEmail | Boolean | Does system send consent email to User email address provided? |
sendCongratulationsEmail | Boolean | Does system send post registration emails? |
roleIdentifier | String | Identifier that defines a given role. Provided by PRIVO Administrator |
emailVerified | Boolean | Trusted partner use to define email verified status. Requires PRIVO contract approval |
minorRegistrations | Collection | Associated child accounts. |
shadowAccount | Boolean | Shuld account start as shadow? |
serviceId | String | Unique identifier assigned to User. |
childServiceId | String | Unique identifier assigned to child User. |
forceActivate | Boolean | Does the account get activated immediately? Requires PRIVO contract approval |
features | Collection | Feature section to define featureIdentifier(s). |
featureIdentifier | String | Identifier used to define a given feature(s). Provided by PRIVO Administrator |
attributes | Collection | Attribute section to define custom attribute(s). |
attributeName | String | Unique name assigned to a custom attribute. Provided by PRIVO Administrator |
value | String | Value assigned to the custom attributeName in use. |
requesterServiceId | String | ServiceId of the requesting User. |
approverServiceId | String | ServiceId of the approving Granter. |
setValue | Boolean | Used for password set/reset API to determine whether set or reset is used. |
emailToResolveCollision | String | Used for password API to resolve conflicts of multiple accounts found. |
suggest | Boolean | Used for username or display name API to suggest additional names if the one provided is already taken. |
verificationTier | Value | Verification tier of the given User - e.g. A_, A_0, A, B, C, D, E, F, G |
Registration Role Context
GET {url}/api/account/registration/{roleIdentifier}/context
Provides registration related information for a given role. The response is useful to determine what attributes and features are tied to a given Partner role definition.
Request Requirements:
Property | Required? | Description |
---|---|---|
roleIdentifier | YES | Unique identifier assigned to the PRIVO role (provided by a PRIVO Administrator) |
featureIdentifiers | NO | Comma-separated list of feature identifiers (provided by a PRIVO Administrator). If omitted, all features will be retrieved. |
Response Notes:
The attributes property in the response contains all the data fields that can be collected about the User for the given role. Attributes may show twice depending on whether they are associated to both the role and a feature associated to the role. The "entityType" value defines how the attribute is tied to the resource. For an example of an attribute showing twice, notice "gender" in the sample response.
The features property in the response contains all the features that are associated to a User who registers for the given role.
API Endpoint:
/api/account/registration/{roleIdentifier}/context
Sample Request:
{{url}}/api/account/registration/someRoleIdentifier
/context?
featureIdentifiers=someFeatureIdentifier,
someOtherFeature
Sample Response:
{
"validationErrors": [],
"status": "success",
"message": null,
"resultCount": -1,
"totalCount": -1,
"entity": {
"role": null,
"profile": null,
"attributes": [
{
"@class": "com.privo.ws.domain.partner.RoleAttribute",
"pii": false,
"collectPostConsent": false,
"rank": 0,
"required": true,
"label": "First Name",
"readOnly": false,
"attribute": {
"@class": "com.privo.ws.domain.partner.AttributeDef",
"added": 1415040943000,
"modified": null,
"id": 20,
"version": 1,
"attribute": "firstName",
"attDefIdentifier": "PRIVOLOCK_firstName",
"label": "First Name",
"rank": -1,
"regex": null,
"maxLength": 50,
"cssClass": null,
"systemAttribute": false,
"placeholderText": "First Name",
"standardAttributeType": "FIRSTNAME",
"categoryType": "STANDARD_ATTRIBUTE",
"widgetType": "TEXT",
"validationType": null,
"optionsType": null,
"optionsDef": null,
"partnerConfigId": 1,
"options": [],
"readOnly": false,
"identifier": "firstName",
"attributeName": "firstName",
"standardAddressAttribute": false,
"referenceDef": null,
"personSiteAttribute": false,
"attributeEntityType": "com.privo.ws.domain.account.PrivoPerson",
"personAttribute": true,
"personAddressAttribute": false,
"attributeId": 20,
"standardAttribute": true,
"_name": "attributeDef"
},
"added": 1587658550000,
"modified": null,
"version": 1,
"identifier": "firstName.someRoleIdentifier",
"roleId": 91,
"entityIdentifier": "someRoleIdentifier",
"entityId": 91,
"appliedTo": "REQUESTER",
"entityType": "RoleAttribute",
"attributeName": "firstName",
"attributeId": 20,
"standardAttribute": true,
"systemAttribute": false,
"standardAttributeType": "FIRSTNAME",
"auditString": "RoleAttribute[PrivoRole[91]: someRoleIdentifier 20]: ",
"options": [],
"maxLength": 50,
"attDefIdentifier": "PRIVOLOCK_firstName",
"placeholderText": "First Name",
"regex": null,
"categoryType": "STANDARD_ATTRIBUTE",
"cssClass": null,
"optionsDef": null,
"referenceDef": null,
"optionsType": null,
"validationType": null,
"widgetType": "TEXT",
"personSiteAttribute": false,
"attributeEntityType": "com.privo.ws.domain.account.PrivoPerson",
"personAttribute": true,
"personAddressAttribute": false,
"_name": "attributeDef"
},
{
"@class": "com.privo.ws.domain.partner.RoleAttribute",
"pii": false,
"collectPostConsent": false,
"rank": 2,
"required": false,
"label": "Gender",
"readOnly": false,
"attribute": {
"@class": "com.privo.ws.domain.partner.AttributeDef",
"added": 1415040943000,
"modified": 1541709803000,
"id": 17,
"version": 11375,
"attribute": "gender",
"attDefIdentifier": "PRIVOLOCK_gender",
"label": "Gender",
"rank": 3,
"regex": null,
"maxLength": 100,
"cssClass": null,
"systemAttribute": false,
"placeholderText": "Gender",
"standardAttributeType": "GENDER",
"categoryType": "STANDARD_ATTRIBUTE",
"widgetType": "RADIO",
"validationType": null,
"optionsType": "USER_DEFINED",
"optionsDef": null,
"partnerConfigId": 1,
"options": [
{
"_id": 5135,
"id": "1",
"value": "Male"
},
{
"_id": 5134,
"id": "2",
"value": "Female"
}
],
"readOnly": false,
"identifier": "gender",
"attributeName": "gender",
"standardAddressAttribute": false,
"referenceDef": null,
"personSiteAttribute": false,
"attributeEntityType": "com.privo.ws.domain.account.PrivoPerson",
"personAttribute": true,
"personAddressAttribute": false,
"attributeId": 17,
"standardAttribute": true,
"_name": "attributeDef"
},
"added": 1587658550000,
"modified": null,
"version": 1,
"identifier": "gender.someRoleIdentifier",
"roleId": 91,
"entityIdentifier": "someRoleIdentifier",
"entityId": 91,
"appliedTo": "REQUESTER",
"entityType": "RoleAttribute",
"attributeName": "gender",
"attributeId": 17,
"standardAttribute": true,
"systemAttribute": false,
"standardAttributeType": "GENDER",
"auditString": "RoleAttribute[PrivoRole[91]: someRoleIdentifier 17]: ",
"options": [
{
"_id": 5135,
"id": "1",
"value": "Male"
},
{
"_id": 5134,
"id": "2",
"value": "Female"
}
],
"maxLength": 100,
"attDefIdentifier": "PRIVOLOCK_gender",
"placeholderText": "Gender",
"regex": null,
"categoryType": "STANDARD_ATTRIBUTE",
"cssClass": null,
"optionsDef": null,
"referenceDef": null,
"optionsType": "USER_DEFINED",
"validationType": null,
"widgetType": "RADIO",
"personSiteAttribute": false,
"attributeEntityType": "com.privo.ws.domain.account.PrivoPerson",
"personAttribute": true,
"personAddressAttribute": false,
"_name": "attributeDef"
},
{
"@class": "com.privo.ws.domain.partner.FeatureAttribute",
"pii": false,
"collectPostConsent": false,
"rank": 1,
"required": true,
"label": "Email",
"readOnly": false,
"attribute": {
"@class": "com.privo.ws.domain.partner.AttributeDef",
"added": 1416431583000,
"modified": 1452718502000,
"id": 491,
"version": 1,
"attribute": "email",
"attDefIdentifier": "PRIVOLOCK_email",
"label": "Email",
"rank": 0,
"regex": "",
"maxLength": 255,
"cssClass": "",
"systemAttribute": false,
"placeholderText": "Email",
"standardAttributeType": "EMAIL",
"categoryType": "STANDARD_ATTRIBUTE",
"widgetType": "EMAIL",
"validationType": null,
"optionsType": null,
"optionsDef": null,
"partnerConfigId": 1,
"options": [],
"readOnly": false,
"identifier": "email",
"attributeName": "email",
"standardAddressAttribute": false,
"referenceDef": null,
"personSiteAttribute": false,
"attributeEntityType": "com.privo.ws.domain.account.PrivoPerson",
"personAttribute": true,
"personAddressAttribute": false,
"attributeId": 491,
"standardAttribute": true,
"_name": "attributeDef"
},
"added": 1569604397000,
"modified": null,
"version": 1,
"appliedTo": "REQUESTER",
"identifier": "email.someOtherFeature",
"entityIdentifier": "someOtherFeature",
"entityId": 74808,
"entityType": "FeatureAttribute",
"featureId": 74808,
"attributeName": "email",
"attributeId": 491,
"standardAttribute": true,
"systemAttribute": false,
"standardAttributeType": "EMAIL",
"auditString": "FeatureAttribute[PrivoFeature[7808]: someOtherFeature Critter Newsletter 491]: ",
"options": [],
"maxLength": 255,
"attDefIdentifier": "PRIVOLOCK_email",
"placeholderText": "Email",
"regex": "",
"categoryType": "STANDARD_ATTRIBUTE",
"cssClass": "",
"optionsDef": null,
"referenceDef": null,
"optionsType": null,
"validationType": null,
"widgetType": "EMAIL",
"personSiteAttribute": false,
"attributeEntityType": "com.privo.ws.domain.account.PrivoPerson",
"personAttribute": true,
"personAddressAttribute": false,
"_name": "attributeDef"
},
{
"@class": "com.privo.ws.domain.partner.FeatureAttribute",
"pii": false,
"collectPostConsent": false,
"rank": 0,
"required": true,
"label": "Gender",
"readOnly": false,
"attribute": {
"@class": "com.privo.ws.domain.partner.AttributeDef",
"added": 1415040943000,
"modified": 1541709803000,
"id": 17,
"version": 11375,
"attribute": "gender",
"attDefIdentifier": "PRIVOLOCK_gender",
"label": "Gender",
"rank": 3,
"regex": null,
"maxLength": 100,
"cssClass": null,
"systemAttribute": false,
"placeholderText": "Gender",
"standardAttributeType": "GENDER",
"categoryType": "STANDARD_ATTRIBUTE",
"widgetType": "RADIO",
"validationType": null,
"optionsType": "USER_DEFINED",
"optionsDef": null,
"partnerConfigId": 1,
"options": [
{
"_id": 58135,
"id": "1",
"value": "Male"
},
{
"_id": 58134,
"id": "2",
"value": "Female"
}
],
"readOnly": false,
"identifier": "gender",
"attributeName": "gender",
"standardAddressAttribute": false,
"referenceDef": null,
"personSiteAttribute": false,
"attributeEntityType": "com.privo.ws.domain.account.PrivoPerson",
"personAttribute": true,
"personAddressAttribute": false,
"attributeId": 17,
"standardAttribute": true,
"_name": "attributeDef"
},
"added": 1569604397000,
"modified": null,
"version": 1,
"appliedTo": "REQUESTER",
"identifier": "gender.someFeatureIdentifier",
"entityIdentifier": "someFeatureIdentifier",
"entityId": 16851,
"entityType": "FeatureAttribute",
"featureId": 16851,
"attributeName": "gender",
"attributeId": 17,
"standardAttribute": true,
"systemAttribute": false,
"standardAttributeType": "GENDER",
"auditString": "FeatureAttribute[PrivoFeature[74808]: someFeatureIdentifier Critterboard Catchers 17]: ",
"options": [
{
"_id": 58135,
"id": "1",
"value": "Male"
},
{
"_id": 58134,
"id": "2",
"value": "Female"
}
],
"maxLength": 100,
"attDefIdentifier": "PRIVOLOCK_gender",
"placeholderText": "Gender",
"regex": null,
"categoryType": "STANDARD_ATTRIBUTE",
"cssClass": null,
"optionsDef": null,
"referenceDef": null,
"optionsType": "USER_DEFINED",
"validationType": null,
"widgetType": "RADIO",
"personSiteAttribute": false,
"attributeEntityType": "com.privo.ws.domain.account.PrivoPerson",
"personAttribute": true,
"personAddressAttribute": false,
"_name": "attributeDef"
},
{
"@class": "com.privo.ws.domain.partner.RoleAttribute",
"pii": false,
"collectPostConsent": false,
"rank": 1,
"required": true,
"label": "Birth Date",
"readOnly": false,
"attribute": {
"@class": "com.privo.ws.domain.partner.AttributeDef",
"added": 1439217831000,
"modified": 1466086315000,
"id": 441,
"version": 2,
"attribute": "birthDate",
"attDefIdentifier": "PRIVOLOCK_birthDate",
"label": "Birth Date",
"rank": 1,
"regex": null,
"maxLength": 1,
"cssClass": "",
"systemAttribute": false,
"placeholderText": "Birth Date",
"standardAttributeType": "BIRTHDATE",
"categoryType": "STANDARD_ATTRIBUTE",
"widgetType": "DATE",
"validationType": null,
"optionsType": null,
"optionsDef": null,
"partnerConfigId": 1,
"options": [],
"readOnly": false,
"identifier": "birthDate",
"attributeName": "birthDate",
"standardAddressAttribute": false,
"referenceDef": null,
"personSiteAttribute": false,
"attributeEntityType": "com.privo.ws.domain.account.PrivoPerson",
"personAttribute": true,
"personAddressAttribute": false,
"attributeId": 441,
"standardAttribute": true,
"_name": "attributeDef"
},
"added": 1587658550000,
"modified": null,
"version": 1,
"identifier": "birthDate.someRoleIdentifier",
"roleId": 91,
"entityIdentifier": "someRoleIdentifier",
"entityId": 91,
"appliedTo": "REQUESTER",
"entityType": "RoleAttribute",
"attributeName": "birthDate",
"attributeId": 441,
"standardAttribute": true,
"systemAttribute": false,
"standardAttributeType": "BIRTHDATE",
"auditString": "RoleAttribute[PrivoRole[91]: someRoleIdentifier 441]: ",
"options": [],
"maxLength": 1,
"attDefIdentifier": "PRIVOLOCK_birthDate",
"placeholderText": "Birth Date",
"regex": null,
"categoryType": "STANDARD_ATTRIBUTE",
"cssClass": "",
"optionsDef": null,
"referenceDef": null,
"optionsType": null,
"validationType": null,
"widgetType": "DATE",
"personSiteAttribute": false,
"attributeEntityType": "com.privo.ws.domain.account.PrivoPerson",
"personAttribute": true,
"personAddressAttribute": false,
"_name": "attributeDef"
}
],
"features": [
{
"id": 16851,
"identifier": "someFeatureIdentifier",
"featureAttributes": [],
"category": "Standard",
"access": "Gated",
"featurePromotion": "Standard",
"activeByDefault": false,
"termsOfServiceUrl": "https://partner.com/terms-of-use/",
"privacyPolicyUrl": "https://partner.com/privacy-policy/",
"dataUsagePolicyUrl": "https://partner.com/terms-of-use/",
"title": "Critterboard Catchers",
"summary": "Critter Chase Leaderboard",
"description": "Want to be on the leaderboard? Get a high score and we will share your accomplishment with the Critter Chase World. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam ultricies mauris at dui eleifend tincidunt. Donec quis est ac leo dapibus egestas. Aenean pellentesque eget tellus non lobortis. Nam eget maximus eros, non pharetra purus. Fusce eget sapien pretium, condimentum lectus et, egestas lectus. Donec vel diam ut velit posuere placerat ut maximus tellus.",
"dataUseDescription": "Gender will be used to fulfill this feature.",
"active": true,
"startDate": 1431302400000,
"endDate": null,
"promoteAtRegistration": false,
"displayOrder": 2,
"price": 0.0,
"freemium": false,
"premium": false,
"premiumOptIn": false,
"site": {
"_id": 1,
"siteIdentifier": "siteIdentifier",
"siteToken": null,
"partnerConfigId": null,
"shortDescription": null,
"siteInfo": null,
"features": [],
"roles": [],
"added": 1587660048396,
"modified": null,
"name": null,
"displayNamePolicy": null,
"description": null,
"active": false,
"roleGateEnabled": false,
"ageGateEnabled": false,
"ageMode": null,
"groupEnabled": false,
"consentExpireDays": 365,
"verificationExpireDays": 30,
"lgsCookieHours": 0,
"lgsCookieDays": 0,
"saveVerificationAddressEnabled": false,
"redirectDelay": 0,
"playNowEnabled": true,
"lgsSignIn": false,
"hidden": false,
"prospectPurgeDays": 3,
"webUrl": null,
"loginUrl": null,
"requireConfirmedEmail": false,
"socialLogin": false,
"socialAttributes": [],
"logoUrl": null,
"emailLogoUrl": null,
"minIntendedAge": 2,
"maxIntendedAge": 13,
"organization": null,
"verificationMethods": [],
"emailFromName": null,
"signInOption": false,
"defaultJurisdictionId": null,
"useCompanyJurisdiction": false,
"useDefaultJurisdiction": false,
"maxChildAge": 0,
"maxTeenAge": 0,
"id": 9999,
"version": null,
"identifier": "siteIdentifier",
"auditString": "Site[9999]: null",
"defaultPrivoSite": false
},
"siteId": 9999,
"featureId": 16851
},
{
"id": 74808,
"identifier": "someOtherFeature",
"featureAttributes": [],
"category": "Optional",
"access": "NonGated",
"featurePromotion": "Optional",
"activeByDefault": false,
"termsOfServiceUrl": "https://partner.com/terms-of-use/",
"privacyPolicyUrl": "https://partner.com/privacy-policy/",
"dataUsagePolicyUrl": "https://partner.com/terms-of-use/",
"title": "Critter Newsletter",
"summary": "Critter Chase Newsletter Membership",
"description": "When an email address is provided then you can start to receive the Critter Newsletter. The monthly newsletter is packed full of activities and information that will keep you engaged and entertained the whole month through. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam ultricies mauris at dui eleifend tincidunt. Donec quis est ac leo dapibus egestas. Aenean pellentesque eget tellus non lobortis. Nam eget maximus eros, non pharetra purus. Fusce eget sapien pretium, condimentum lectus et, egestas lectus. Donec vel diam ut velit posuere placerat ut maximus tellus.",
"dataUseDescription": "Email address and gender will be used to fulfill this feature.",
"active": true,
"startDate": 1531612800000,
"endDate": null,
"promoteAtRegistration": false,
"displayOrder": 3,
"price": 0.0,
"freemium": false,
"premium": false,
"premiumOptIn": false,
"site": {
"_id": 2,
"siteIdentifier": "siteIdentifier",
"siteToken": null,
"partnerConfigId": null,
"shortDescription": null,
"siteInfo": null,
"features": [],
"roles": [],
"added": 1587660048400,
"modified": null,
"name": null,
"displayNamePolicy": null,
"description": null,
"active": false,
"roleGateEnabled": false,
"ageGateEnabled": false,
"ageMode": null,
"groupEnabled": false,
"consentExpireDays": 365,
"verificationExpireDays": 30,
"lgsCookieHours": 0,
"lgsCookieDays": 0,
"saveVerificationAddressEnabled": false,
"redirectDelay": 0,
"playNowEnabled": true,
"lgsSignIn": false,
"hidden": false,
"prospectPurgeDays": 3,
"webUrl": null,
"loginUrl": null,
"requireConfirmedEmail": false,
"socialLogin": false,
"socialAttributes": [],
"logoUrl": null,
"emailLogoUrl": null,
"minIntendedAge": 2,
"maxIntendedAge": 13,
"organization": null,
"verificationMethods": [],
"emailFromName": null,
"signInOption": false,
"defaultJurisdictionId": null,
"useCompanyJurisdiction": false,
"useDefaultJurisdiction": false,
"maxChildAge": 0,
"maxTeenAge": 0,
"id": 9999,
"version": null,
"identifier": "siteIdentifier",
"auditString": "Site[9999]: null",
"defaultPrivoSite": false
},
"siteId": 9999,
"featureId": 74808
}
]
},
"responseTimestamp": 1587660048540
}
Register Account
POST {url}/api/account
This API registers accounts in the PRIVO system. The attributes contained in the body of the request (in conjunction to proper scopes) determine what type of account is being created. Child accounts created with this API will be orphaned until such time that a Parent (e.g. Granter) gets involved in the registration flow. Use the /parent variable described below to access the most robust child registraions.
Depending on your Partner configuration either birthDate_YYYYMMDD or roleIdentifier is required to create
an account, but not both. If both are supplied, then they must match the defined role being accessed. If more than one
role exists that overlaps an age range then the roleIdentifier must be present.
NOTE: This API works best for Teen and Adult registrations.
Attributes defined in the body of the request must be set in Partner configurations in order for them to properly be used during API access. Missing attributes will be collected from the Granter during the permissioning process or from the User post-consent. See About Attributes section for more information about attribute use.
Child Registrations
When attempting to register a child with a parent associated then we suggest that you use the following API variation.
POST {url}/api/account/parent
This /parent API will register the child(ren) desired and automatically generate/associate the desired parent account. By using the /parent API, Partners can define specific attributes and features to assign to, not only, the child account but also to the Parent account. The JSON response will include the Parent serviceId.
API Endpoint:
/api/account
Parent API Endpoint:
/api/account/parent
Sample Adult Account Request with Birth Date:
POST {{url}}/api/account/
Body:
{
"firstName":"Sally",
"userName":"coolTeacher47",
"birthDate_YYYYMMDD":"19720605",
"email":"someEmailAddress@someDomain.com",
"gender":"Female"
}
Sample Adult Account Request with Role Identifier:
POST {{url}}/api/account/
Body:
{
"firstName":"Sally",
"userName":"coolTeacher47",
"roleIdentifier":"someAdultRole",
"email":"someEmailAddress@someDomain.com",
"gender":"Female"
}
Sample Adult JSON Response:
{
"validationErrors": [0],
"status": "success",
"message": null,
"resultCount": -1,
"totalCount": -1,
"entity": {
"serviceId": "5a334e59484......46673d3d",
"fullyRegistered": false,
"role": {}
"roleId": 101,
"roleIdentifier": "someAdultRole",
"shadowAccount": false,
"firstName": "Sally",
"lastName": null,
"middleInitial": null,
"email": "someEmailAddress@someDomain.com",
"emailVerified": false,
"birthDate": 76550400000,
"userName": "coolTeacher47",
"active": false,
"isMinor": false,
"isTeen": false,
"isTeacher": false,
"isStudent": false,
"gender": 2,
"genderType": "Female",
"registrationRole": "Adult",
"address": {
"streetAddress1": null,
"streetAddress2": null,
"city": null,
"stateProvince": null,
"postalCode": null,
"country": "US"
}
"creationDate": 1396282972776,
"activationDate": null,
"verificationTier": "A",
"id": "5a334e59484......46673d3d",
"fullName": "Sally",
"features": [],
"pin": "VYJJ30K",
"displayNames": [],
"modifiedDate": 1587751273264,
"consentUrl": "{url}/e/r/a?token=6c3254537931724b.....64413d3d",
"consentRequests": [],
"characteristics": [
"ADULT"
],
"connectedProfiles": [],
"hasPasscode": false,
"hasPassword": false
}
}
Sample Child Account Request with Parent:
POST {{url}}/api/account/parent
Body:
{
"email": "parentEmailAddress@someDomain.com",
"roleIdentifier": "someParentRoleIdentifier",
"minorRegistrations": [
{
"sendParentEmail": true,
"firstName": "Tammy",
"roleIdentifier": "someChildRoleIdentifier",
"gender": "2",
"birthDate_YYYYMMDD":"20100329",
"features":[
{"featureIdentifier": "someFeatureIdentifier"}
],
"attributes": [
{
"attributeName": "partnerPin",
"value": "someValue"
}
]
}
]
}
Sample Child/Parent JSON Response:
{
"validationErrors": [0],
"status": "success",
"message": null,
"resultCount": -1,
"totalCount": -1,
"entity": {
"serviceId": "5a334e59484......46673d3d",
"fullyRegistered": false,
"role": {}
"roleId": 101,
"roleIdentifier": "someParentRole",
"shadowAccount": false,
"firstName": null,
"lastName": null,
"middleInitial": null,
"email": "parentEmailAddress@someDomain.com",
"emailVerified": false,
"birthDate": 76550400000,
"userName": null,
"active": false,
"isMinor": false,
"isTeen": false,
"isTeacher": false,
"isStudent": false,
"gender": null,
"genderType": null,
"registrationRole": "Parent",
"address": {
"streetAddress1": null,
"streetAddress2": null,
"city": null,
"stateProvince": null,
"postalCode": null,
"country": "US"
}
"creationDate": 1396282972776,
"activationDate": null,
"verificationTier": "A",
"id": "5a334e59484......46673d3d",
"fullName": null,
"features": [],
"pin": "VYJJ30K",
"displayNames": [],
"modifiedDate": 1587751273264,
"consentUrl": "{url}/e/r/a?token=6c3254537931724b.....64413d3d",
"consentRequests": [
{
"status": "PENDING",
"pin": 1587753484188,
"consentDate": 1587753484188
}
],
"characteristics": [
"PARENT"
],
"connectedProfiles": [
{
"serviceId": "70424836326a4......16b413d3d",
"fullyRegistered": false,
"roleId": 12235,
"roleIdentifier": "someChildRoleIdentifier",
"shadowAccount": false,
"firstName": "Tammy",
"lastName": null,
"emailVerified": false,
"emailPending": false,
"birthDate": 1269820800000,
"isMinor": true,
"isTeen": false,
"isTeacher": false,
"isStudent": false,
"gender": "Female",
"address": {
"added": 1587753484215,
"modified": null,
"id": 38026172,
"version": 1,
"streetAddress1": null,
"streetAddress2": null,
"city": null,
"stateProvince": null,
"region": null,
"postalCode": null,
"country": "US",
"homePhoneNumber": null,
"faxNumber": null
},
"locale": "en_US",
"verificationTier": "A_",
"features": [],
"personId": 72888768,
"personSiteId": 75519658,
"attributes": [
{
"@class": "com.privo.ws.domain.account.Attribute",
"added": 1587753484322,
"modified": 1587753484322,
"id": 513541,
"version": 1,
"value": "JA50D3W",
"scope": "GLOBAL",
"personId": null,
"attributeDef": {
"@class": "com.privo.ws.domain.partner.AttributeDef",
"added": 1565028836000,
"modified": 1565034652000,
"id": 89326,
"version": 2,
"attribute": "partnerPin",
"attDefIdentifier": "partnerUserPin",
"label": "Partner PIN",
"rank": 10,
"regex": "",
"maxLength": 100,
"cssClass": "",
"systemAttribute": false,
"placeholderText": "Partner PIN",
"standardAttributeType": null,
"categoryType": "CUSTOM_ATTRIBUTE",
"widgetType": "TEXT",
"validationType": null,
"optionsType": null,
"optionsDef": null,
"partnerConfigId": 4907,
"options": [],
"readOnly": false,
"identifier": "partnerPin",
"attributeName": "partnerPin",
"standardAddressAttribute": false,
"referenceDef": null,
"personSiteAttribute": false,
"attributeEntityType": null,
"personAttribute": false,
"personAddressAttribute": false,
"attributeId": 894326,
"standardAttribute": false,
"_name": "attributeDef"
},
"attributeDefId": null,
"attributeName": null,
"attribute": {},
"attributeId": null
}
],
"siteId": {partnerSiteId},
"siteName": "Some Partner Site",
"siteToken": "4b766f4c565037......35158673d3d",
"displayNames": [],
"modifiedDate": 1587753484225,
"consentRequests": [
{
"status": "PENDING",
"pin": 1587753484295,
"consentDate": 1587753484295
}
],
"characteristics": [
"CHILD"
],
"connectedProfiles": [],
"fullName": "Tammy",
"minor": true,
"id": "70424836326a4......16b413d3d",
"registrationRole": "someChildRoleIdentifier",
"student": false,
"teen": false,
"teacher": false,
"hasPasscode": false,
"hasPassword": false,
"birthDate_yyyy": "2010"
}
],
"hasPasscode": false,
"hasPassword": false
}
}
Add Child To Adult Account
POST {url}/api/account
This API adds a new child to an existing adult account that does not already have children. The attributes contained in the body of the request (in conjunction to proper scopes) determine what type of account is being created. Child accounts created with this API will be assigned to the adult associated to the " parentEmail".
Depending on your Partner configuration either birthDate_YYYYMMDD or roleIdentifier is required to create an account, but not both. If both are supplied, then they must match the defined role being accessed. If more than one role exists that overlaps an age range then the roleIdentifier must be present.
Attributes defined in the body of the request must be set in Partner configurations in order for them to properly be used during API access. Missing attributes will be collected from the Granter during the permissioning process or from the User post-consent. See About Attributes section for more information about attribute use.
API Endpoint:
/api/account
Sample Add Child to Existing Adult Account:
POST {{url}}/api/account/
Body:
{
"firstName":"Sally",
"userName":"coolkid23",
"birthDate_YYYYMMDD":"20100605",
"gender":"Female"
"email":"someEmailAddress@someDomain.com",
"parentEmail":"someExistingAdult@someDomain.com"
}
Sample Add Child JSON Response:
{
"validationErrors": [0],
"status": "success",
"message": null,
"resultCount": -1,
"totalCount": -1,
"entity": {
"serviceId": "5a334e59484......46673d3d",
"fullyRegistered": false,
"role": {}
"roleId": 101,
"roleIdentifier": "someChildRole",
"shadowAccount": false,
"firstName": "Sally",
"lastName": null,
"middleInitial": null,
"email": "someEmailAddress@someDomain.com",
"emailVerified": false,
"birthDate": 76550400000,
"userName": "coolkid23",
"active": false,
"isMinor": false,
"isTeen": false,
"isTeacher": false,
"isStudent": false,
"gender": 2,
"genderType": "Female",
"registrationRole": "Adult",
"address": {
"streetAddress1": null,
"streetAddress2": null,
"city": null,
"stateProvince": null,
"postalCode": null,
"country": "US"
}
"creationDate": 1396282972776,
"activationDate": null,
"verificationTier": "A",
"id": "5a334e59484......46673d3d",
"fullName": "Sally",
"features": [],
"displayNames": [],
"modifiedDate": 1587751273264,
"consentUrl": "{url}/e/r/a?token=6c3254537931724b.....64413d3d",
"consentRequests": [],
"characteristics": [
"CHILD"
],
"connectedProfiles": [],
"hasPasscode": false,
"hasPassword": false
}
}
Register Shadow Account
POST {url}/api/account/shadow
This API registers a shadow account. A shadow account is an account that has no credentials. Shadow accounts mimic or represent some other account or entity. No PII is collected when registering a shadow account.
Request Requirements:
Property | Required? | Description |
---|---|---|
roleIdentifier | YES | Unique identifier assigned to the PRIVO role Provided by a PRIVO Administrator |
NOTE: All other attributes or properties are Optional. See About Attributes and Features for more information.
Shadow Account API Endpoint:
/api/account/shadow
Sample Shadow Account Request:
POST {{url}}/api/account/shadow
Body:
{
"roleIdentifier":"someShadowAccountRole"
}
Sample Shadow JSON Response:
{
"validationErrors": [0],
"status": "success",
"message": null,
"resultCount": -1,
"totalCount": -1,
"entity": {
"serviceId": "5a334e59484......46673d3d",
"fullyRegistered": false,
"role": {}
"roleId": 101,
"roleIdentifier": "someShadowAccountRole",
"shadowAccount": true,
"firstName": null,
"lastName": null,
"middleInitial": null,
"email": null,
"emailVerified": false,
"birthDate": null,
"userName": null
"active": false,
"isMinor": false,
"isTeen": false,
"isTeacher": false,
"isStudent": false,
"gender": null,
"genderType": null
"registrationRole": "Child",
"address": {
"streetAddress1": null,
"streetAddress2": null,
"city": null,
"stateProvince": null,
"postalCode": null,
"country": "US"
}
"creationDate": 1396282972776,
"activationDate": null,
"verificationTier": "A_",
"id": "5a334e59484......46673d3d",
"fullName": null,
"features": [],
"displayNames": [],
"modifiedDate": 1587751273264,
"consentUrl": "{url}/e/r/a?token=6c3254537931724b.....64413d3d",
"consentRequests": [],
"characteristics": [
"CHILD"
],
"connectedProfiles": [],
"hasPasscode": false,
"hasPassword": false
}
}
List Consent Requests
GET {{url}}/api/consent
Endpoint to retrieve consent requests for the currently authenticated User thus requires an authentication token for access. If current User is a child their consent requests are returned, otherwise, the current Users associated child consent requests are returned.
The request may contain the following OPTIONAL query parameters.
Request Requirements:
Property | Required? | Description |
---|---|---|
siteId | NO | The ID that identifies the Partner service. |
status | NO | Defines the consent request status. Allowed values: APPROVED, DENIED, EXPIRED, PENDING |
A correct request will generate a HTTP response code of 200 (Ok).
List Consents Endpoint:
/api/consent
Sample List Consents Request:
GET {{url}}/api/consent?
siteId=9999
&status=APPROVED
Sample JSON Response:
{
"entity": [
{
"requester": {
"serviceId": "5459366c77......85a673d3d"
},
"approver": {
"serviceId": "3974387665......67f123d0d"
},
"consentRequestType": "FEATURE",
"status": "APPROVED",
"approvedDate": 1396223608367,
"deniedDate": null,
"expiredDate": null,
"childFeatures": [
"feature"
:
{
"id": 100
...
}
]
}
]
}
Child Requests Feature
POST {{url}}/api/consent
Creates additional child feature consent requests for the currently authenticated child User, thus requires an authentication token for access. Use case for this request is when a child wants some additional consent for a feature not previously consented to.
The API requires that the desired featureId(s) are provided in the body of the request. Note: this API does not send out the consent email to the parent for approval. In order for the consent email to be sent to the Granter (i.e. Parent), the Resend Consent Request API must be run.
The request body must contain the desired feature(s) being requested. Features are defined in an array by their ** featureId**.
A correct request will generate a HTTP response code of 200 (Ok) and the consent request will exist for the User. If the Granter then lands on the PRIVO permission page they will be able to grant the additional consent. This can be achieved by either resending the consent email or by the Granter, on their own, managing their account.
User Requests Feature Endpoint:
/api/consent
Sample Feature Consent Request:
POST {{url}}/api/consent
Request Body:
{
"features":[
{"featureId":441}
]
}
Sample JSON Response:
{
"entity":[
{
"status": "PENDING",
"consentRequestType": "FEATURE",
"approvedDate": null,
"deniedDate": null,
"expiredDate": null,
"accountOrigin": "PRIVO",
"childAccountId": null,
"childFirstName": "Frank",
"childBirthDate": null,
"description": "Some Child Feature",
"emailPlusVerified": null,
"feature": {
"added": 1415806315000,
"modified": null,
"id": 441,
"version": 10,
"optInDefault": false,
"allowHolderEmailAsOther": false,
"allowDuplicateRegistrations": false,
"emailPlusDays": 3,
"noResponseDeleteDays": 30,
"bounceEmailResendTimes": 3,
"optInDelayDays": 3,
"optInReminderDays": 3,
"optOutRetainDays": 0,
"emailCampaignId": null,
"identifier": "someFeatureIdentifier",
"campaignFieldMappings": null,
"category": "Optional",
"access": "Gated",
"featurePromotion": "Optional",
"activeByDefault": true,
"selfGrantorTier": "D",
"grantorTier": "D",
"webhooks": [],
"termsOfServiceUrl": "https://partnerSite.com/terms-of-use/",
"privacyPolicyUrl": "https://partnerSite.com/privacy-policy/",
"dataUsagePolicyUrl": "https://partnerSite.com/terms-of-use/",
"title": "Some Child Feature Name",
"summary": "Feature Summary",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus et lorem posuere sem varius egestas a et elit. Quisque eu porta sapien. Nunc aliquet a nisl sed semper. Nulla purus felis, vehicula ac congue in, porttitor id sapien. Suspendisse condimentum facilisis dui non accumsan. In nec feugiat ligula, id suscipit metus. Vivamus sit amet velit ac quam ornare vulputate a a lorem. Nulla molestie eget tortor vel cursus.",
"dataUseDescription": "This is a data description section. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus et lorem posuere sem varius egestas a et elit. Quisque eu porta sapien. Nunc aliquet a nisl sed semper.",
"active": true,
"promoteAtRegistration": false,
"displayOrder": 5,
"price": 0.0,
"freemium": false,
"premium": false,
"premiumOptIn": false,
"consentRequestFreqDays": 1,
"siteId": 9999,
"optional": true,
"featureId": 441,
"standard": false,
"childAudience": false,
"minorAudience": false
},
}
]
}
Resend Consent Request
GET {url}//api/consent/resend
This service sends/resends the consent email to the Grantor (i.e. Parent/Adult/Self).
A use case example for this is informing a parent that their child has registered on a Partner site. A request to send/resend the Your child has registered... consent email should contain the following query parameters.
Request Requirements:
Property | Required? | Description |
---|---|---|
requesterServiceId | YES | Defines the given User (Requester) whom is requesting consent, e.g. child serviceId. |
approverServiceId | YES, IF... | If approver email is not present Defines the given Granter (Parent) who needs to provide consnet for the User. |
YES, IF... | If approverServiceId is not present Email address associated to the Granter. |
A correct request will generate a HTTP response code of 200 (Ok). Otherwise a 404 (Not Found) is generated.
Resend Consent Request Endpoint:
/api/consent/resend
Sample Resend Child Consent Request:
GET {{url}}/api/consent/resend?
approverServiceId=5459366c77......85a673d3d
&requesterServiceId=3974387665......67f123d0d
Sample Resend Self Consent Request:
GET {{url}}/api/consent/resend?
requesterServiceId=3974387665......67f123d0d
&email=someEmailAddress@someDomain.com
Sample Resend Consent JSON Response:
{
"validationErrors": [
0
],
"status": "success",
"message": null,
"resultCount": -1,
"totalCount": -1,
"entity": null,
"responseTimestamp": 1588247984911
}
Grant Feature Consent
PUT {{url}}/api/consent/{childServiceId}
Grants consent to one or more feature requests and must be run with the requesting Users serviceId. The consenting adult (Granter) is the currenlty authenticated User. If Feature Id's are provided in the body of the request then those given feature(s) will be activated.
The verificationRequestId parameter is supplied in the case when the granting adult user has verified at a higher level of verification.
Grant Consent Request Endpoint:
/api/consent/{childServiceId}
Sample Grant Child Consent Request:
PUT {{url}}/api/consent/5459366c77......85a673d3d
Body:
{
"verificationRequestId":"1234abcd",
"features":[
{"featureId":100},
{"featureId":200}
]
}
Sample Grant Consent JSON Response:
{
"validationErrors": [
0
],
"status": "success",
"message": null,
"resultCount": -1,
"totalCount": -1,
"entity": null,
"responseTimestamp": 1588247984911
}
Revoke Feature Consent
DELETE /api/consent/{childServiceId}
Revokes consent for one or more feature consent requests. The revoking adult is the currenlty authenticated adult user. This API can also be run with Partner authentication. When Feature Id's are present in the body of the request the result will be revocation of those given features. Otherwise, all features will be revoked.
Revoke Consent Request Endpoint:
/api/consent/{userServiceId}
Sample Revoke Consent Request:
DELETE {{url}}/api/consent/5459366c77......85a673d3d
Body:
{
"features":[
{"featureId":100},
{"featureId":200}
]
}
Sample Revoke Consent JSON Response:
{
"validationErrors": [
0
],
"status": "success",
"message": null,
"resultCount": -1,
"totalCount": -1,
"entity": null,
"responseTimestamp": 1588247984911
}
Requirements
iOS 13+, macOS 10.15+, watchOS 13+, tvOS 13+, Swift 5+
You also will need to get credentials from PRIVO to work with this SDK ( see Usage Documentation)
Installation
You can import it as Swift Package in Xcode:
- Select xcodeproj
- Go to File -> Swift Packages -> Add Package Dependency
- Use git@github.com:Privo/privo3-ios-sdk.git as repo URL
- Select master branch as a source
Alternativly you can import it in Package.swift file:
Add this code to the dependencies value of your Package.swift:
dependencies: [
.package(url: "git@github.com:Privo/privo3-ios-sdk.git", .branch("master"))
]
Usage
Imprort PRIVO SDK in files where you want to use it:
import PrivoSDK
Init PrivoSDK with credentials provided by PRIVO:
Privo.initialize( settings: PrivoSettings(serviceIdentifier: "{{value}}", envType: .Prod, apiKey: "{{value}}"))
serviceIdentifier - required partner identifier in PRIVO system. Must be received from the PRIVO
envType - type on enviroment on what are you going to use PRIVO
Can be Int or Prod
For integration purpose partners should use Int Enviroment
For production apps partners should use Prod Enviroment
apiKey - an optional parameter that is used in the identity verification module (can be nil)
Note: Make sure that you have initialized the PRIVO SDK before any usage of it. It is a good idea to do this immediately after launching the application.
Init PrivoSDK:
@main
struct DemoApp: App {
init() {
// You can add your settings rirht here
Privo.initialize(
settings: PrivoSettings(
serviceIdentifier: "{{value}}",
envType: .Int,
apiKey: "{{value}}"
)
)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Auth Module
Privo.auth.getToken() - returns a previously issued token, if it have not expired. Can return nil if token wasn't issued or expired
Privo.auth.logout() - Logout and clean previously issued token.
Privo.auth.renewToken() - Renew token. Return token and it's status.
Privo.auth.renewToken() { tokenStatus in
let token: String? = tokenStatus?.token
let isRenewed: Bool? = tokenStatus?.isRenewed
}
Privo.auth.showAuth() - Shows a modal window. User is prompted to Sign In inside this modal window. It returns a new
user token as a result
Privo.auth.showRegister() - Shows a modal window. User is prompted to Create an Account inside this modal
window
NOTE: this dialog should be closed from completion callback. (This can be done after a short delay. In this case, the
user will be able to read content of the congratulations page)
Auth Module Swift UI Components
PrivoAuthButton - swift ui button element that opens a modal window by clicking on it. User is prompted to Sign In
inside this modal window. Depending on the integration settings, this button can be customized with branded PRIVO styles
by default.
PrivoAuthButton params:
label - customizable Swift UI Label element for the button. It can contains text and any styling inside it.
onFinish - optional completion callback that will provide token if Auth was successful. Otherwise it will provide
nil.
closeIcon - optional image for close icon in modal dialog. If nothing is specified, the default image will be
used.
PrivoAuthButton(label: {
Text("Sign In")
}, onFinish: { token in
self.token = token
}).padding()
PrivoRegisterButton - swift ui button element that opens a modal window by clicking on it. User is prompted to
Create an account inside this modal window. Depending on the integration settings, this button can be customized with
branded PRIVO styles by default.
PrivoRegisterButton params:
isPresented - binding state value that shows status of modal window presentation. Changed to true automatically when
user press button.
Should be changed to false when you want to close the modal dialog.
label - customizable Swift UI Label element for the button. It can contains text and any styling inside it.
onFinish - optional completion handler that will provide token if Auth was successful. Otherwise it will provide
nil.
You can change isPresented state value to false inside this callback.
You also can change isPresented after some delay. In this case user will be able to see "Congratulation" page content
inside modal dialog.
closeIcon - optional image for close icon in modal dialog. If nothing is specified, the default image will be
used.
PrivoRegisterButton(isPresented: $showRegistration, label: {
Text("Register")
}){
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
showRegistration = false
}
}.padding()
Age Gate Module
Age Gate Flow Diagrams
Simple Age Gate Flow Diagram
Age Gate Flow Diagram (with Age Recheck)
Age Gate MultiUser Flow Diagram
Age Gate SDK example
Privo.ageGate.getStatus(userIdentifier) { s in
event = s
}
// ...
let data = CheckAgeData(
userIdentifier: userIdentifier,
birthDateYYYYMMDD: birthDate,
countryCode: country
)
Privo.ageGate.run(data) { s in
event = s
}
// ...
Privo.ageGate.recheck(data) { s in
event = s
}
Sample SDK Response:
{
"id": "861dc238-...-c1dfe",
"status": "Allowed",
"extUserId": "9ede0f0-...a78",
//optional
"countryCode": "US"
//optional
}
Age Gate Module
Class PrivoAgeGate
Instance Method getStatus(userIdentifier:nickname:)
The method allows checking the existing Age Gate status.
func getStatus(
userIdentifier: String?,
nickname: String? = nil
) async throws -> AgeGateEvent
Return Value
Parameters
userIdentifier - external user identifier (please don't use empty string ("") as a value. It will cause an error. We support real values or nil if you don't have it)
nickname - optional parameter with default value nil. Please, use nickname only in case of multi-user integration. Please don't use empty string "" in it.
Throws
There is only one exception type - PrivoError.
PrivoError.cancelled
is thrown if cancelled withTask.cancel()
.PrivoError.incorrectInputData(AgeGateError)
is thrown if the data submitted for processing is not correct.
Discussion
Concurrency Note
You can call this method from asynchronous code. This method has similar one, which resembles the completion handler method for calling from synchronous code.
See more information about concurrency and asynchronous code in Swift.
Instance Method getStatus(userIdentifier:nickname:completionHandler:errorHandler:)
The method allows checking the existing Age Gate status.
func getStatus(
userIdentifier: String?,
nickname: String? = nil,
completionHandler: @escaping (AgeGateEvent) -> Void,
errorHandler: ((Error) -> Void)? = nil
)
Return Value
Parameters
userIdentifier - external user identifier (please don't use empty string ("") as a value. It will cause an error. We support real values or nil if you don't have it)
nickname - optional parameter with default value nil. Please, use nickname only in case of multi-user integration. Please don't use empty string "" in it.
completionHandler - closure that is used to handle an asynchronous operation result and takes the AgeGateEvent instance as an input argument.
errorHandler - optional parameter with default value nil. Called instead of the completionHandler when an error occurs. Takes an Error instance as input argument.
Discussion
Concurrency Note
For calling method from asynchronous code you could use a similar.
See more information about concurrency and asynchronous code in Swift.
Instance Method run(_:)
The method runs the Age Gate check. If the birth date is passed by a partner or filled in by a user, the method will return the status. If the birth date is not passed, a user will be navigated to the corresponding entry window and forced to fill in the birthday field.
run(_ data: CheckAgeData) async throws -> AgeGateEvent
Return Value
AgeGateEvent
Returns a status, which takes into consideration a user age and configuration parameters defined by a partner.
Parameters
data - instance of type CheckAgeData
Throws
There is only one exception type - PrivoError.
PrivoError.cancelled
is thrown if cancelled withTask.cancel()
.PrivoError.incorrectInputData(AgeGateError)
is thrown if the data submitted for processing is not correct.
Discussion
Concurrency Note
You can call this method from asynchronous code. This method has a similar one, which resembles the completion handler method for calling from synchronous code.
See more information about concurrency and asynchronous code in Swift.
Instance Method run(_:completionHandler:)
The method runs the Age Gate check. If the birth date is passed by a partner or filled in by a user, the method will return the status. If the birth date is not passed, a user will be navigated to the corresponding entry window and forced to fill in the birthday field.
func run(
_ data: CheckAgeData,
completionHandler: @escaping (AgeGateEvent?) -> Void
)
Parameters
completionHandler - A closure to execute. Nil indicates that a failure occurred. Status parameter takes into consideration a user age and a configuration set defined by a partner.
Discussion
Concurrency Note
For calling method from asynchronous code you could use a similar.
See more information about concurrency and asynchronous code in Swift.
Instance Method recheck(_:)
The method allows rechecking data if the birth date provided by a user was updated.
func recheck(_ data: CheckAgeData) async throws -> AgeGateEvent
Return Value
Parameters
data - instance of type CheckAgeData
Throws
There is only one exception type - PrivoError.
PrivoError.cancelled
is thrown if cancelled withTask.cancel()
.PrivoError.incorrectInputData(AgeGateError)
is thrown if the data submitted for processing is not correct.- Could throws
PrivoError.incorrectInputData(AgeGateError.agIdNotFound)
.
Discussion
Concurrency Note
You can call this method from asynchronous code. This method has a similar one, which resembles the completion handler method for calling from synchronous code.
See more information about concurrency and asynchronous code in Swift.
Instance Method recheck(_:completionHandler:)
The method allows rechecking data if the birth date provided by a user was updated.
func recheck(
_ data: CheckAgeData,
completionHandler: @escaping (AgeGateEvent?) -> Void
)
Parameters
completionHandler - a closure to execute. Nil indicates a failure has occured.
Discussion
Concurrency Note
For calling method from asynchronous code you could use a similar.
See more information about concurrency and asynchronous code in Swift.
Instance Method linkUser(userIdentifier:agId:nickname:)
The method links a user to specified userIdentifier. It is used in multi-user flow in case an account creation (on a partner side) happens after the Age Gate flow. Please note that linkUser can be used only for users that don't have userIdentifier yet. It is impossible to change the userIdentifier after it was obtained.
func linkUser(
userIdentifier: String,
agId: String,
nickname: String?
) async throws -> AgeGateEvent
Return Value
Parameters
userIdentifier - external user identifier (please don't use empty string ("") as a value. It will cause an error. We support real values or null if you don't have it)
agId - age gate identifier that you get as a response from sdk on previous steps
nickname - optional field, nickname (please use only in case of multi-user integration. Please don't use empty string "" in it)
Throws
There is only one exception type - PrivoError.
PrivoError.cancelled
is thrown if cancelled withTask.cancel()
.PrivoError.incorrectInputData(AgeGateError)
is thrown if the data submitted for processing is not correct.
Discussion
Concurrency Note
You can call this method from asynchronous code. This method has a similar one, which resembles the completion handler for calling from synchronous code.
See more information about concurrency and asynchronous code in Swift.
Instance Method linkUser(userIdentifier:agId:nickname:completionHandler:errorHandler:)
The method will link a user to a specified userIdentifier. It is used in multi-user flow in case an account creation (on a partner side) happens after the Age Gate flow. Please note that linkUser can be only used for users that don't have userIdentifier yet. It is impossible to change the userIdentifier after it was obtained.
func linkUser(
userIdentifier: String,
agId: String,
nickname: String?,
completionHandler: @escaping (AgeGateEvent) -> Void,
errorHandler: ((Error) -> Void)? = nil
)
Parameters
userIdentifier - external user identifier (please don't use empty string ("") as a value. It will cause an error. We support real values or null if you don't have it)
agId - age gate identifier that you get as a response from sdk on previous steps
nickname - optional field, nickname (please use only in case of multi-user integration. Please don't use empty string "" in it)
errorHandler - optional parameter with default value nil. Called instead of the completionHandler when an error occurs. Takes an Error instance as input argument.
completionHandler - closure that is used to handle an asynchronous operation result and takes the AgeGateEvent instance as an input argument.
Discussion
Concurrency Note
For calling method from asynchronous code you could use a similar.
See more information about concurrency and asynchronous code in Swift.
Instance method showIdentifierModal(userIdentifier:nickname:)
The method will show a modal dialog with user age gate identifier (can be used to contact customer support)
func showIdentifierModal(
userIdentifier: String?,
nickname: String? = nil
)
Parameters
userIdentifier - optional field, external user identifier (please don't use empty string ("") as a value. It will cause an error. We support real values or null if you don't have it)
nickname - optional field, nickname (please use only in case of multi-user integration. Please don't use empty string "" in it)
Instance method hide()
The method allows a partner to hide the Age Gate widget.
func hide()
Age Gate Module
struct AgeGateEvent
struct AgeGateEvent {
agId: String?
ageRange: AgeRange?
countryCode: String?
nickname: String?
status: AgeGateStatus
userIdentifier: String?
}
struct AgeRange: Decodable, Encodable, Hashable {
start: Int
end: Int
jurisdiction: String?
}
Instance Properties
status - enum with values: “Undefined”, “Blocked”, “Allowed”, “Сanceled”, “Pending”, “ConsentRequired”, “ConsentApproved”, “ConsentDenied”, “AgeVerificationRequired”, “AgeVerified”, “AgeBlocked”, “IdentityVerificationRequired”, “IdentityVerified”.
Please check the Age Gate Status Description here
userIdentifier - String field, external user identifier
countryCode - String field, two-letter country code ISO 3166-1 alpha-2.
nickname - String field, nickname
agId - String field, age gate identifier
Age Gate Module
struct CheckAgeData
struct CheckAgeData {
userIdentifier: String?
nickname: String?
birthDateYYYYMMDD: String?
birthDateYYYYMM: String?
birthDateYYYY: String?
age: Int?
countryCode: String?
}
Instance Properties
userIdentifier - optional field, external user identifier (please don't use empty string ("") as a value. It will cause an error. We support real values or nil if you don't have it)
nickname - optional field, nickname (please use only in case of multi-user integration. Please don't use empty string "" in it)
birthDateYYYYMMDD - optional field, external user birth date in "yyyy-MM-dd" format
birthDateYYYYMM - optional field, external user birth date in "yyyy-MM" format
birthDateYYYY - optional field, external user birth date in "yyyy" format. Derived birthDate will be calculated with Dec 31 by default
age - optional field, external user age format. Derived birthDate will be calculated with current day and month by default
countryCode - optional field, two-letter country code ISO 3166-1 alpha-2
Age Gate Module
enum PrivoError
enum PrivoError
Enumeration Cases
case cancelled
An error that indicates a task was cancelled with Task.cancel()
.
Thrown only by those methods that support cancellation and this is explicitly stated in the documentation.
Cancellation example:
let task = Task {
do {
let result = try await methodThatSupportsCancellation()
} catch let privoError as PrivoError {
if privoError == .cancelled {
// handle the cancellation
}
}
}
...
// later in code
task.cancel()
case incorrectInputData(Error)
case networkConnectionProblem(Error?)
case noInternetConnection
enum AgeGateError
enum AgeGateError
Enumeration Cases
case agIdNotFound
Attempting to invoke a method for the first time which used agId that has not yet been generated. Try adding a call before the current one so that the method generating agId is called first.
case incorrectAge
case incorrectDateOfBirht
case notAllowedEmptyStringAgId
case notAllowedEmptyStringNickname
case notAllowedEmptyStringUserIdentifier
case notAllowedMultiUserUsage
Age Verification Module
Method 1: Check Status
Privo.ageVerification.getStatus(userIdentifier, completionHandler)
The method allows checking the existing Age Verification status.
Age Verification Status entry parameters:
userIdentifier - optional String field, external user identifier
completionHandler - closure which used to handle the result of an asynchronous operation and takes as input argument AgeVerificationEvent instance.
struct AgeVerificationEvent {
let status: AgeVerificationStatus
let profile: AgeVerificationProfile?
}
struct AgeVerificationProfile {
let userIdentifier: String?
let firstName: String?
let email: String?
let birthDateYYYYMMDD: String? // “yyyy-MM-dd” format
let phoneNumber: String?
}
status - enum with values: “Undefined” , “Pending” , “Confirmed” , “Declined” , “Canceled”.
profile - child profile verified by PRIVO:
userIdentifier - optional field, external user identifier
birthDateYYYYMMDD - optional field, external user birth date in "yyyy-MM-dd" format
email - optional field, child user email address
firstName - optional field, child user first name
phoneNumber - optional field, child user phone number in the full international format (E.164, e.g.
“+17024181234”)
Please check the Age Verification Status Description here
Method 2: Run
Privo.ageVerification.run(profile, completionHandler)
struct AgeVerificationProfile {
let userIdentifier: String?
let firstName: String?
let email: String?
let birthDateYYYYMMDD: String?
let phoneNumber: String?
}
The method runs the Age Verification check and returns the following statuses, depending on the user’s age and set by a
partner configuration parameters: “Undefined” , “Pending” , “Confirmed” , “Declined” , “Canceled”.
AgeVerificationProfile entry (profile) parameters:
userIdentifier - optional field, external user identifier
birthDateYYYYMMDD - optional field, external user birth date in "yyyy-MM-dd" format
firstName - optional field, child user first name
email - optional field, child user email address
birthDateYYYYMMDD - optional field, child user birth date in “yyyy-MM-dd” format
phoneNumber - optional field, child user phone number in the full international format (E.164, e.g.
“+17024181234”)
completionHandler receives AgeVerificationEvent? instance, where nil indicates a failure has occurred.
struct AgeVerificationEvent {
let status: AgeVerificationStatus
let profile: AgeVerificationProfile?
}
struct AgeVerificationProfile {
let userIdentifier: String?
let firstName: String?
let email: String?
let birthDateYYYYMMDD: String? // “yyyy-MM-dd” format
let phoneNumber: String?
}
status - enum with values: “Undefined” , “Pending” , “Confirmed” , “Declined” , “Canceled”.
profile - child profile verified by PRIVO:
userIdentifier - optional field, external user identifier
birthDateYYYYMMDD - optional field, external user birth date in "yyyy-MM-dd" format
email - optional field, child user email address
firstName - optional field, child user first name
phoneNumber - optional field, child user phone number in the full international format (E.164, e.g.
“+17024181234”)
Please check the Age Verification Status Description here
Age Verification SDK example
Privo.ageVerification.getStatus(userIdentifier) { response in
status = response.status
}
// ...
let profile = AgeVerificationProfile(
userIdentifier: userIdentifier,
firstName: firstName,
email: email,
birthDateYYYYMMDD: getBirhDate(),
phoneNumber: phoneNumber
)
Privo.ageVerification.run(profile) { response in
status = response.status
}
Identity Verification Module
Privo.verification.showVerification(profile: UserVerificationProfile) {
(verificationEvents: [VerificationEvent]) in
}
Used to show PRIVO Verification Modal Dialog
UserVerificationProfile - optional user predefined profile
public struct UserVerificationProfile: Encodable {
public var firstName: String?
public var lastName: String?
public var birthDateYYYYMMDD: String? // Optional date string in the "yyyy-MM-dd" date-format
public var email: String?
public var postalCode: String?
public var phone: String? // Optional phone number in E.164 format. Example: "+12133734253"
public var partnerDefinedUniqueID: String? // Optional unique identifier passed by Partner and returned in all responses by PRIVO.
}
completion - closure which used to handle the result of an asynchronous operation and takes as input argument array of VerificationEvent instancies.
public struct VerificationEvent: Decodable, Hashable {
public let event: VerificationEventType // the event type. Can be: verifyInitialized, verifyError, verifyCancel, verifyComplete, verifyDone
public let result: VerificationResult? // verification result data from PRIVO
public let data: String? // The string representation of result object.
public let errorCode: String? // Error Code
public let errorMessage: String? // Error message
}
Verification Events:
verifyInitialized - When the verification widget has initialized.
verifyCancel - When user has canceled the verification.
verifyComplete - When user has successfully completed the verification process and has been verified.
verifyDone - When the user has completed the verification and closed the verification widget.
verifyError - If an error occurs. See Error Codes section.
verifyPrintPreview
Possible Error Codes:
Error Code | Error Message |
---|---|
10001 | Invalid API Key or access_token |
10002 | Missing site_id parameter |
10003 | Unexpected error |
10100 | Invalid email address |
10101 | Misconfigured verification methods |
412 | Too many attempts |
public struct VerificationResult: Decodable, Hashable {
public let serviceId: String? // If the user has opted to save their verification status by adding a password, then a serviceId will be generated
public let verificationResponse: VerificationResponse // Verification response data
}
public struct VerificationResponse: Decodable, Hashable {
public let verified: Bool // user verification status
public let requestID: String // Unique identifier for the verification request. The partner should retain this value for traceability.
public let transactionID: String // Unique identifier for the transaction. Partner can retain this value for traceability.
public let verificationMethod: VerificationMethodType // The verification method chosen by the user. Possible values: CreditCard, DriversLicense, SSN, CorporateEmail, PrintForm, Phone
public let matchOutcome: VerificationOutcome // Specific outcome for the verification request. Possible values: Pass, Pending (when the user has chosen an offline method of verification, such as Phone or PrintForm, matchOutcome will be ‘Pending’).
public let requestTimestamp: Date // Date of the completed verification request.
public let locale: String // Location of the user as defined by their browser settings.
public let matchCode: String? // A code that identifies the field groups that are matched in the verification request. May be nil.
public let redirectUrl: String? // Return URL address passed by partner to send the user directly following onVerifyDone event. May be nil.
public let message: String? // For debug reasons - if error occurs error message will be provided here. May be nil.
public let partnerDefinedUniqueID: String? // Value passed by partner in config of the verification request. PRIVO returns this value in the onVerifyComplete event. Can be nil.
//Applicable to offline methods only
public let identificationNumber: String? // nique number provided to user when an offline verification method is chosen. This value can be used by Partner and PRIVO to identify the given pending request.
public let attemptId: Int? // Identifier used to notate the attempt request.
public let requestIdentifier: String
}
Method to get status of the current user (can be configured to be blocked after N attempts)
Privo.verification.getUserLimits(externalYserId: String) { limits in }
response: UserLimits - User Limits Information
public struct UserLimits: Decodable, Encodable, Hashable {
public let isOverLimit: Bool
public let limitType: LimitType
public let retryAfter: Int?
}
public enum LimitType: String, Codable {
case IV
case Auth
}
Identity Verification SDK example
let profile = UserVerificationProfile(
firstName: "{{value}}",
lastName: "{{value}}",
birthDateYYYYMMDD: "1970-01-01",
email: "{{value}}",
postalCode: "{{value}}",
phone: "{{value}}",
partnerDefinedUniqueID: "{{value}}",
)
Privo.verification.showVerificationModal(profile) {
verificationEvents in
for verificationEvent in verificationEvents {
verificationEvent.event
verificationEvent.result
verificationEvent.data
verificationEvent.errorCode
verificationEvent.errorMessage
}
}
Identity Verification Module Swift UI Components
PrivoVerificationButton(label: {
Text("Show Verification")
}, onFinish: {
events in
self.events = events
}, profile: nil).padding()
Requirements
Android API Level 23+
You also will need to get credentials from PRIVO to work with this SDK (see Usage Documentation)
Installation
Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
Add the dependency:
dependencies {
implementation 'com.github.Privo:/privo3-android-sdk:{{release}}'
}
Note:
release - current release version tag. For example 0.0.1
Alternatively you can install it manually:
- Open your project in Android Studio
- Download the library (using Git, or a zip archive to unzip)
- Go to File > Import Module and import the library as a module
- Right-click your app in project view and select "Open Module Settings"
- Click the "Dependencies" tab and then the '+' button
- Select "Module Dependency"
- Select "SDK" (not SDK Project)
To verify manual installation, check:
- check settings.gradle file, it should contain:
include ':sdk'
project(':sdk').projectDir = new File(settingsDir, '../../privo3-android-sdk/sdk')
NOTE: ../../privo3-android-sdk/sdk -- path to SDK. It maybe different in your env.
- check build.gradle file, it should contain:
dependencies {
implementation project(':sdk')
}
Usage
Init PrivoSDK with credentials provided by PRIVO:
val settings = PrivoSettings(serviceIdentifier = {{value}}, envType = EnvironmentType.Prod, apiKey = {{value}})
Privo.initialize(settings)
serviceIdentifier - required partner identifier in PRIVO system. Must be received from the PRIVO
envType - type on environment on what are you going to use PRIVO
Can be Int or Prod
For integration purpose partners should use Int Environment
For production apps partners should use Prod Environment
apiKey - an optional parameter that is used in the identity verification module (can be null)
Note: Make sure that you have initialized the PRIVO SDK before any usage of it. It is a good idea to do this immediately after launching the application, for example via androidx.startup.InitializationProvider
Init PrivoSDK:
import android.content.Context
import androidx.startup.Initializer
import com.privo.sdk.Privo
import com.privo.sdk.model.EnvironmentType
import com.privo.sdk.model.PrivoSettings
class PrivoSDKInitializer: Initializer<Unit> {
override fun create(context: Context) {
val settings = PrivoSettings({{value}}, EnvironmentType.Int, {{value}})
Privo.initialize(settings)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
// No dependencies on other libraries.
return emptyList()
}
}
Auth Module
First you need to import and instantiate PRIVO auth module:
import com.privo.sdk.PrivoAuth
You can instantiate the PRIVO auth module with Context:
val auth = PrivoAuth(this)
Functionality:
auth.getToken() - returns a previously issued token, if it hasn't expired. Can return null if token wasn't issued or
expired
auth.renewToken { status ->
setTokenText(status?.token)
}
Renew token. Return token and it's status
auth.showLogin { token ->
setTokenText(token)
}
Shows a modal window. User is prompted to Sign In inside this modal window.
auth.showRegister { dialog ->
dialog.hide()
}
Shows a modal window. User is prompted to Create an Account inside this modal window.
NOTE: this dialog should be closed from completion callback. (This can be done after a short delay. In this case, the
user will be able to read content of the congratulations page.)
auth.logout() - Logout and clean previously issued token.
Auth Module Usage Example:
class AuthActivity : AppCompatActivity() {
private lateinit var binding: ActivityAuthBinding
private lateinit var auth: PrivoAuth
private lateinit var loadingDialog: LoadingDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
auth = PrivoAuth(this)
loadingDialog = LoadingDialog(this)
binding = ActivityAuthBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
initViews()
}
private fun initViews() {
binding.registerButton.setOnClickListener {
auth.showRegister { dialog ->
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
dialog.hide()
}, 5000)
}
}
binding.signInButton.setOnClickListener {
auth.showLogin { token ->
setTokenText(token)
}
}
binding.getTokenButton.setOnClickListener {
val token = auth.getToken()
setTokenText(token)
}
binding.renewTokenButton.setOnClickListener {
loadingDialog.show()
setTokenText(null)
auth.renewToken { status ->
loadingDialog.hide()
setTokenText(status?.token)
}
}
binding.logoutButton.setOnClickListener {
auth.logout()
setTokenText(null)
}
}
fun setTokenText(token: String?) {
binding.tokenView.text = "Token: ${token ?: "Empty"}"
}
}
Age Gate Module
First you need to import and instantiate PRIVO auth module:
import com.privo.sdk.PrivoAgeGate
You can instantiate the PRIVO auth module with Context:
val ageGate = PrivoAgeGate(this)
Method 1: Check Status
ageGate.getStatus(userIdentifier,nickname)
The method allows checking the existing Age Gate status.
Age Gate Status entry parameters:
userIdentifier - optional field, external user identifier (please don't use empty string ("") as a value. It will cause an error. We support real values or null if you don't have it)
nickname - optional field, nickname (please use only in case of multi-user integration. Please don't use empty string "" in it)
Age Gate Service response:
data class AgeEvent (
val status: AgeCheckStatus,
val userIdentifier: String?,
val nickname: String?,
val agId: String?,
val ageRange: AgeRange?,
)
class AgeRange (
val start: Int,
val end: Int,
val jurisdiction: String?,
)
status - enum with values: “Undefined”, “Blocked”, “Allowed”, “Сanceled”, “Pending”,
“ConsentRequired”,“ConsentApproved”, “ConsentDenied”, “AgeVerificationRequired”, “AgeVerified”, “AgeBlocked”,
“IdentityVerificationRequired”, “IdentityVerified”.
userIdentifier - String field, external user identifier
nickname - String field, nickname
agId - String field, age gate identifier
Please check the Age Gate Status Description here
Method 2: Run
Privo.ageGate.run(data)
data class CheckAgeData (
val userIdentifier: String?,
val nickname: String?,
val birthDateYYYYMMDD: String?,
val birthDateYYYYMM: String?,
val birthDateYYYY: String?,
val age: Int?,
val countryCode: String?,
)
The method runs the Age Gate check: if the birthdate is passed by a partner or filled in by a user, the method will
return the status "Undefined", "Blocked", "MultiUserBlocked", "AgeEstimationBlock", "Allowed", "Canceled", "Pending", "ConsentRequired", "ConsentApproved", "
ConsentDenied, "AgeVerificationRequired", "AgeVerified", "AgeBlocked", "IdentityVerificationRequired", "
IdentityVerified", depending on the user’s age and set by a partner configuration parameters.
If the birthdate is not passed, a user will be navigated to the corresponding entry window and forced to fill in the
birthday field.
CheckAgeData entry parameters:
userIdentifier - optional field, external user identifier (please don't use empty string ("") as a value. It will cause an error. We support real values or null if you don't have it)
nickname - optional field, nickname (please use only in case of multi-user integration. Please don't use empty string "" in it)
birthDateYYYYMMDD - optional field, external user birth date in "yyyy-MM-dd" format
birthDateYYYYMM - optional field, external user birth date in "yyyy-MM" format
birthDateYYYY - optional field, external user birth date in "yyyy" format. Derived birthDate will be calculated with Dec 31 by default
age - optional field, external user age format. Derived birthDate will be calculated with current day and month by default
countryCode - optional field, two-letter country code (ISO 3166-1
alpha-2 Wiki).
Response:
data class AgeEvent (
val status: AgeGateStatus,
val userIdentifier: String?,
val nickname: String?;
val agId: String?,
val ageRange: AgeRange?,
)
data class AgeRange (
val start: Int,
val end: Int,
val jurisdiction: String?,
)
status - enum, "Undefined", "Blocked", "MultiUserBlocked", "AgeEstimationBlock", "Allowed", "Сanceled", "Pending", "ConsentRequired", "ConsentApproved", "
ConsentDenied, "AgeVerificationRequired", "AgeVerified", "AgeBlocked", "IdentityVerificationRequired", "
IdentityVerified"
userIdentifier - optional field, external user identifier
nickname - optional field, nickname
agId - optional field, age gate identifier
Please check the Age Gate Status Description here
Method 3: Age Recheck
ageGate.recheck(data)
data class CheckAgeData (
val userIdentifier: String?,
val nickname: String?;
val birthDateYYYYMMDD: String?,
val birthDateYYYYMM: String?,
val birthDateYYYY: String?,
val age: Int?,
val countryCode: String?,
)
The method allows rechecking data if the birth date provided by a user was updated.
Age Gate Recheck entry parameters:
userIdentifier - optional field, external user identifier (please don't use empty string ("") as a value. It will cause an error. We support real values or null if you don't have it)
nickname - optional field, nickname (please use only in case of multi-user integration. Please don't use empty string "" in it)
birthDateYYYYMMDD - optional field, external user birth date in "yyyy-MM-dd" format
birthDateYYYYMM - optional field, external user birth date in "yyyy-MM" format
birthDateYYYY - optional field, external user birth date in "yyyy" format. Derived birthDate will be calculated with Dec 31 by default
age - optional field, external user age format. Derived birthDate will be calculated with current day and month by default
countryCode - optional field, two-letter country code (ISO 3166-1 alpha-2 Wiki).
Response:
data class AgeEvent (
val status: AgeGateStatus,
val userIdentifier: String?,
val nickname: String?,
val agId: String?,
val ageRange: AgeRange?,
)
data class AgeRange (
val start: Int,
val end: Int,
val jurisdiction: String?,
)
status - enum, "Undefined", "Blocked", "MultiUserBlocked", "AgeEstimationBlock", "Allowed", "Сanceled", "Pending", "ConsentRequired", "ConsentApproved", "
ConsentDenied, "AgeVerificationRequired", "AgeVerified", "AgeBlocked", "IdentityVerificationRequired", "
IdentityVerified"
userIdentifier - optional field, external user identifier
nickname - optional field, nickname
agId - optional field, age gate identifier
Please check the Age Gate Status Description here
Method 4: Age Gate Link User
ageGate.linkUser(userIdentifier,agId,nickname)
The method will link user to specified userIdentifier. It's used in multi-user flow, when account creation (on partner side) happens after age-gate. Please note that linkUser can be used only for users that doesn't have userIdentifier jet. You can't change userIdentifier if user already have it.
userIdentifier - external user identifier (please don't use empty string ("") as a value. It will cause an error. We support real values or null if you don't have it)
agId - age gate identifier that you get as a response from sdk on previous steps
nickname - optional field, nickname (please use only in case of multi-user integration. Please don't use empty string "" in it)
Method 5: Age Gate Show Identifier Modal
ageGate.showIdentifierModal(userIdentifier, nickname)
The method will show a modal dialog with user age gate identifier (can be used to contact customer support)
Age Gate Show Identifier Modal parameters:
userIdentifier - optional field, external user identifier (please don't use empty string ("") as a value. It will cause an error. We support real values or null if you don't have it)
nickname - optional field, nickname (please use only in case of multi-user integration. Please don't use empty string "" in it)
Method 6: Hide Age Gate Widget
ageGate.hide()
The method allows a partner to hide the Age Gate widget.
Age Gate Flow Diagrams
Simple Age Gate Flow Diagram
Age Gate Flow Diagram (with Age Recheck)
Age Gate MultiUser Flow Diagram
Age Gate SDK example:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ageGate = PrivoAgeGate(this)
}
// ...
ageGate.getAgeStatus(userIdentifier) {
processNewStatus(it?.status)
}
// ...
val data = CheckAgeData(userIdentifier,birthDate,countryCode)
ageGate.run(data) {
processNewStatus(it?.status)
}
// ...
val data = CheckAgeData(userIdentifier,birthDate,countryCode)
ageGate.recheck(data) {
processNewStatus(it?.status)
}
Sample SDK Response:
{
"id": "861dc238-...-c1dfe",
"status": "Allowed",
"extUserId": "9ede0f0-...a78",
//optional
"countryCode": "US"
//optional
}
Age Verification Module
First, you need to import and instantiate PRIVO auth module:
import com.privo.sdk.PrivoAgeVerification
You can instantiate the PRIVO auth module with Context:
val age Verification = PrivoAgeVerification(this)
Method 1: Check Status
ageVerification.getStatus(userIdentifier)
The method allows checking the existing Age Verification status.
Age Gate Status entry parameters:
userIdentifier - optional String field, external user identifier
Age Verification Service response:
data class AgeVerificationEvent (
val status: AgeVerificationStatus;
val profile : AgeVerificationProfile?
)
data class AgeVerificationProfile (
val userIdentifier: String?,
val firstName: String?,
val email: String?,
val birthDateYYYYMMDD: String?, // "yyyy-MM-dd" format
val phoneNumber: String? // in the full international format (E.164, e.g. "+17024181234")
)
status - enum with values: “Undefined” , “Pending” , “Confirmed” , “Declined” , “Canceled”.
profile - child profile verified by PRIVO:
userIdentifier - optional field, external user identifier
birthDateYYYYMMDD - optional field, external user birthdate in "yyyy-MM-dd" format
email - optional field, child user email address
firstName - optional field, child user first name
phoneNumber - optional field, child user phone number in the full international format (E.164, e.g.
“+17024181234”)
Please check the Age Verification Status Description here
Method 2: Run
Privo.ageVerification.run(data)
data class AgeVerificationProfile {
val userIdentifier?: String,
val birthDateYYYYMMDD?: String,
val email?: String,
val firstName?: String,
val phoneNumber?: string; // in the full international format (E.164, e.g. “+17024181234”)
}
The method runs the Age Verification check and returns the following statuses, depending on the user’s age and set by a
partner configuration parameters: “Undefined” , “Pending” , “Confirmed” , “Declined” , “Canceled”.
AgeVerificationProfile entry (profile) parameters:
userIdentifier - optional field, external user identifier
birthDateYYYYMMDD - optional field, external user birthdate in "yyyy-MM-dd" format
email - optional field, child user email address
firstName - optional field, child user first name
phoneNumber - optional field, child user phone number in the full international format (E.164, e.g.
“+17024181234”)
Response:
data class AgeVerificationEvent (
val status: AgeVerificationStatus;
val profile : AgeVerificationProfile?
)
data class AgeVerificationProfile (
val userIdentifier: String?,
val firstName: String?,
val email: String?,
val birthDateYYYYMMDD: String?, // "yyyy-MM-dd" format
val phoneNumber: String? // in the full international format (E.164, e.g. "+17024181234")
)
status - enum, “Undefined” , “Pending” , “Confirmed” , “Declined” , “Canceled”.
profile - child profile verified by PRIVO:
userIdentifier - optional field, external user identifier
birthDateYYYYMMDD - optional field, external user birthdate in "yyyy-MM-dd" format
email - optional field, child user email address
firstName - optional field, child user first name
phoneNumber - optional field, child user phone number in the full international format (E.164, e.g.
“+17024181234”)
Please check the Age Verification Status Description here
Age Gate SDK example:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ageVerification = PrivoAgeVerification(this)
}
// ...
ageVerification.getStatus(userIdentifier) {
processNewStatus(it?.status)
}
// ...
val data = AgeVerificationProfile(
userIdentifier,
birthDateYYYYMMDD,
email,
firstName,
phoneNumber
)
ageVerification.run(data) {
processNewStatus(it?.status)
}
Identity Verification Module
First you need to import and instantiate PRIVO verification module:
import com.privo.sdk.PrivoVerification
You can instantiate the PRIVO verification module with Context:
val verification = PrivoVerification(this)
Functionality:
verification.showVerification(profile) { events -> }
Used to show PRIVO Verification Modal Dialog
params: UserVerificationProfile - optional user predefined profile
data class UserVerificationProfile (
var firstName: String?,
var lastName: String?,
var birthDateYYYYMMDD: String?, // Optional date string in the "yyyy-MM-dd" date-format
var email: String?,
var postalCode: String?,
var phone: String?, // Optional phone number in E.164 format. Example: "+12133734253"
var partnerDefinedUniqueID: String? // Optional unique identifier passed by Partner and returned in all responses by PRIVO.
)
response: Array<VerificationEvent> - Array of Verification Events
data class VerificationEvent(
val event: VerificationEventType,
val result: VerificationResult?,
val data: String?,
val errorCode: String?,
val errorMessage: String?
)
Verification Events:
VerifyInitialized - When the verification widget has initialized.
VerifyCancel - When user has canceled the verification.
VerifyComplete - When user has successfully completed the verification process and has been verified.
VerifyDone - When the user has completed the verification and closed the verification widget.
VerifyError - If an error occurs. See Error Codes section.
Possible Error Codes:
Error Code | Error Message |
---|---|
10001 | Invalid API Key or access_token |
10002 | Missing site_id parameter |
10003 | Unexpected error |
10100 | Invalid email address |
10101 | Misconfigured verification methods |
412 | Too many attempts |
Method to get status of the current user (can be configured to be blocked after N attempts)
verification.getUserLimits(externalYserId: String) { limits -> }
response: UserLimits - User Limits Information
class UserLimits (
val isOverLimit: Boolean,
val limitType: LimitType,
val retryAfter: Int?,
)
enum class LimitType(val type: String) {
IV("IV"),
Auth("Auth"),
}
Identity Verification Module Example:
data class VerificationResult(val serviceId : String?, val verificationResponse: VerificationResponse)
data class VerificationResponse(
val verified: Boolean,
val requestID: String,
val transactionID: String,
val verificationMethod: VerificationMethodType,
val matchOutcome: VerificationOutcome,
val requestTimestamp: Long,
val locale: String,
val matchCode: String?,
val redirectUrl: String?,
val message: String?,
val partnerDefinedUniqueID: String?,
//Applicable to offline methods only
val identificationNumber: String?,
val attemptId: Int?
)
class VerificationActivity : AppCompatActivity() {
private val dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.US)
private val sdkDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
private lateinit var binding: ActivityVerificationBinding
private lateinit var verification: PrivoVerification
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
verification = PrivoVerification(this)
binding = ActivityVerificationBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
initViews()
}
private fun initViews() {
val eventsAdapter = VerificationEventsAdapter()
binding.eventsRecycler.adapter = eventsAdapter
binding.showButton.setOnClickListener {
val profile = getTestProfile()
verification.showVerification(profile) { events ->
eventsAdapter.addEvents(events)
}
}
}
private fun getTestProfile(): UserVerificationProfile {
val profile = UserVerificationProfile()
profile.firstName = binding.firstNameField.getOptionalText()
profile.lastName = binding.lastNameField.getOptionalText()
profile.email = binding.emailField.getOptionalText()
profile.postalCode = binding.postalCodeField.getOptionalText()
profile.phone = binding.phoneField.getOptionalText()
profile.partnerDefinedUniqueID = binding.partnerIdField.getOptionalText()
binding.birthDateField.getOptionalText()?.let {
val birthDateYYYYMMDD = sdkDateFormat.format(date)
profile.birthDateYYYYMMDD = birthDateYYYYMMDD
}
return profile
}
}
Using Callbacks / Webhooks
Partners can be notified of consent events through a consent callback mechanism. Partner can setup webhook invocation at the partner config level and must expose an endpoint that accepts a JSON data structure that contains the User Info of the User that has been granted access and the User that granted the access.
Partners can subscribe to multiple verification events on Privo Console (ask PRIVO Customer Support for details). Those events will trigger the webhooks which will be posted to partner provided endpoint with defined security headers.
Webhook Security Header Types:
Type | Definition |
---|---|
* Key/Value | Partner provides secure endpoint URI, endpoint name and corresponding API Key. Webhooks will fire to provided endpoint and return the endpoint name and API Key in the secure header. |
Custom Signed | Partner provides secure endpoint URI. PRIVO supplies a unique API Key to partner. Webhooks will fire to provided endpoint and return the unique custom PRIVO signed API Key in the secure header. |
Bearer Token | Partner provides secure endpoint URI and corresponding API Key. Webhooks will fire to provided endpoint and return the partner provided API Key after "Bearer" in the secure header. |
- Preferred method suggested by PRIVO
Sample Key/Value Header:
{"timestamp":1557934559462,"url":"/echo/v0.1/callback/90001-webhook/","method":"HEAD","headers":{"User-Agent":"Apache-HttpClient/4.5.4 (Java/1.8.0_212)","(webhook_name)":"(webhook_apiKey)","Accept":"text/plain, application/json, application/*+json, */*","Connection":"Keep-Alive","Host":"example.privo.com","content-length":"0","Content-Type":"text/plain"},"content":""}
Sample Custom Signed Header:
{"timestamp":1557934446367,"url":"/echo/v0.1/callback/90001-webhook/","method":"HEAD","headers":{"User-Agent":"Apache-HttpClient/4.5.4 (Java/1.8.0_212)","X-Privo-Webhook-Signature":"(PRIVO_signed_apiKey)","Accept":"text/plain, application/json, application/*+json, */*","Connection":"Keep-Alive","Host":"example.privo.com","content-length":"0","X-Privo-Webhook-Id":"16","Content-Type":"text/plain"},"content":""}
Sample Bearer Token Header:
{"timestamp":1557934665833,"url":"/echo/v0.1/callback/90001-webhook/","method":"HEAD","headers":{"User-Agent":"Apache-HttpClient/4.5.4 (Java/1.8.0_212)","Authorization":"Bearer (webhook_apiKey)","Accept":"text/plain, application/json, application/*+json, */*","Connection":"Keep-Alive","Host":"example.privo.com","content-length":"0","Content-Type":"text/plain"},"content":""}
Sample JSON Response:
{
"requester": {Data Set of Requesting User},
"approver": {Data Set of Granting User},
"permissions": [
{
"on": true,
"consent_date": 1448035241,
"request_date": 1448035241,
"feature_on": true,
"feature_id": 99999,
"feature_identifier": "FEATURE_XYZ",
"feature_name": "A Very Special Feature"
}
],
"request_date": 1448035241
}
Webhook Events & Outcomes
PRIVO fires various webhook events based on a user's interaction with the system. There are two classifications of events:
Verification Events: Verification events are those events that fire when a user has successfully interacted with the PRIVO Verification Widget. For more information see Verificattion Events.
Verification Match Outcomes:
Outcome | Methods | Description |
---|---|---|
Pass | Offline/Online | Verification passed. User has successfully verified either by online or offline method and ready to proceed futher |
Fail | Online | Verification failed. User provided incorrect verification data |
Pending | Offine | Verification submitted for manual review. |
Declined | Offine | Verification declined by administrator. |
Purged | Offine | Verification rejected by expiration policied configured per partner. |
VERIFY_VERIFIED:
{
"webhookId": 1234,
"event": "VERIFY_VERIFIED"
"data": {
"verified": true,
"verificationMethod": "SSN",
"matchOutcome": "Pass",
"matchCode": "6",
"message": null,
"requestID": "35459",
"redirectUrl": null,
"partnerDefinedUniqueID": "(some_identifier)",
"requestTimestamp": 145245796521,
"locale": "en_US",
"identificationNumber": null,
"attemptId": 99247
},
"timestamp" : 1552046947107
}
VERIFY_FAILED:
{
"webhookId": 1234,
"event": "VERIFY_FAILED"
"data": {
"verified": false,
"verificationMethod": "CreditCard",
"matchOutcome": "Fail",
"matchCode": "AuthorizationFailed",
"message": null,
"requestID": "35459",
"redirectUrl": null,
"partnerDefinedUniqueID": "(some_identifier)",
"requestTimestamp": 145245796521,
"locale": "en_US",
"identificationNumber": null,
"attemptId": 99247
},
"timestamp" : 1552046947107
}
VERIFY_ACCOUNT:
{
"webhookId": 1234,
"event": "VERIFY_ACCOUNT"
"data": {
"verified": true,
"verificationMethod": "PrintForm",
"matchOutcome": "Pending",
"matchCode": null,
"message": null,
"requestID": "35459",
"redirectUrl": null,
"partnerDefinedUniqueID": "(some_identifier)",
"requestTimestamp": 145245796521,
"locale": "en_US",
"identificationNumber": null,
"serviceId" : "583951746...626b6a51673d3d",
"attemptId": 99247
}
VERIFY_PENDING:
{
"webhookId": 1234,
"event": "VERIFY_PENDING"
"data": {
"verified": true,
"verificationMethod": "PrintForm",
"matchOutcome": "Pending",
"matchCode": null,
"message": null,
"requestID": "35459",
"redirectUrl": null,
"partnerDefinedUniqueID": "(some_identifier)",
"requestTimestamp": 145245796521,
"locale": "en_US",
"identificationNumber": null,
"attemptId": 99247
}
VERIFY_OFFLINE_VERIFIED:
{
"webhookId": 1234,
"event": "VERIFY_OFFLINE_VERIFIED"
"data": {
"verified": true,
"verificationMethod": "Phone",
"matchOutcome": "Pass",
"matchCode": null,
"message": null,
"requestID": "35459",
"redirectUrl": null,
"partnerDefinedUniqueID": "(some_identifier)",
"requestTimestamp": 145245796521,
"locale": "en_US",
"identificationNumber": "HL5KDQ",
"attemptId": 99247
}
VERIFY_REMOVED:
{
"webhookId": 1234,
"event": "VERIFY_REMOVED"
"data": {
"verified": false,
"verificationMethod": "PrintForm",
"matchOutcome": "Declined",
"matchCode": null,
"message": null,
"requestID": "35459",
"redirectUrl": null,
"partnerDefinedUniqueID": "(some_identifier)",
"requestTimestamp": 145245796521,
"locale": "en_US",
"identificationNumber": "Jones",
"attemptId": 99247
}
VERIFY_PURGED:
{
"webhookId": 1234,
"event": "VERIFY_PURGED"
"data": {
"verified": false,
"verificationMethod": "Phone",
"matchOutcome": "Purged",
"matchCode": null,
"message": null,
"requestID": "35459",
"redirectUrl": null,
"partnerDefinedUniqueID": "(some_identifier)",
"requestTimestamp": 145245796521,
"locale": "en_US",
"identificationNumber": "HL5KDQ",
"attemptId": 99247
}
Consent Events: Consent events are those events that fire when a user has successfully interacted with the PRIVO registration system. Actual status of consent populated via type proprty (see CONSENT_ALL, CONSENT_DECLINE or CONSENT_EXPIRE examples ) and may be one of the following values:
- APPROVED * EXPIRED * PENDING * POSTPONED
Consent Outcomes:
Outcome | Description |
---|---|
PENDING | Consent process was started bit not finised |
EXPIRED | Verification failed. User provided incorrect verification data |
DENIED | Consent was rejected by user or his granter |
APPROVED | Consent approved by user himself or his granter (depending on a user role/age) |
POSTPONED | Consent was postponed as it's not meeting requirements (Offline verification was not approved by admin) |
CONSENT_ALL:
{
"privoEvents:": [
{
"webhookId": 1234,
"event": "CONSENT_ALL",
"data": {
"type": "APPROVED",
"requester": {
"sub": "76617742356c...4e362f64773d3d",
"birthdate": "2010-03-05",
"email_verified": false,
"role_identifier": "(someDefinedRole)",
"permissions": [
{
"on": true,
"consent_time": 1.624294042e9,
"request_time": 1.622745196e9,
"feature_active": true,
"feature_id": 2281.0,
"feature_identifier": "(someDefinedFeatureIdentifier)",
"feature_category": "Standard",
"feature_name": "Earn and Share Theme Acheivements"
},
...
],
"verification_tier": "D"
},
"approver": {
"sub": "50707077364a...76477313454513d3d",
"birthdate": "2003-06-03",
"email_verified": false,
"role_identifier": "(someDefinedRole)",
"verification_tier": "D"
},
"requestTimestamp": 1624294042550
},
"timestamp": 1624294042572
}
]
}
CONSENT_DECLINE:
{
"privoEvents:": [
{
"webhookId": 1234,
"event": "CONSENT_DECLINE",
"data": {
"type": "DENIED",
"requester": {
"sub": "76617742356c...4e362f64773d3d",
"birthdate": "2010-03-05",
"email_verified": false,
"role_identifier": "(someDefinedRole)",
"permissions": [
{
"on": false,
"consent_time": 1.624294042e9,
"request_time": 1.622745196e9,
"feature_active": true,
"feature_id": 2281.0,
"feature_identifier": "(someDefinedFeatureIdentifier)",
"feature_category": "Standard",
"feature_name": "Earn and Share Theme Acheivements"
},
...
],
"verification_tier": "D"
},
"approver": {
"sub": "50707077364a...76477313454513d3d",
"birthdate": "2003-06-03",
"email_verified": false,
"role_identifier": "(someDefinedRole)",
"verification_tier": "D"
},
"requestTimestamp": 1624294042550
},
"timestamp": 1624294042572
}
]
}
CONSENT_EXPIRE:
{
"privoEvents": [
{
"webhookId": 15,
"event": "CONSENT_EXPIRE",
"data": {
"type": "EXPIRED",
"requester": {
"sub": "34486677467263423...a7157513d3d",
"zoneinfo": "America/New_York",
"locale": "en_US",
"updated_at": "1688515878",
"birthdate": "2023-06-13",
"role_identifier": "(someDefinedRole)",
"registration_role": "(someDefinedRole)",
"approved": true,
"shadow_account": false,
"site_token": "536b632f462b6d7...43425839773d3d",
"permissions": [
{
"on": false,
"consent_time": null,
"request_time": 1686658996,
"feature_active": true,
"feature_id": 30308,
"feature_identifier": "(someDefinedFeatureIdentifier)",
"feature_category": "Standard",
"feature_name": "(someDefinedFeatureName)",
"attributes": []
}
...
],
"verification_tier": "A",
"user_profile": {
"birthdate": "2023-06-13",
"registration_role": "(someDefinedRole)",
"role_identifier": "(someDefinedRole)",
"site_token": "536b632f462b6d...3425839773d3d",
"approved": true,
"shadow_account": false,
"verification_tier": "A",
"attributes": [],
"display_names": []
},
"connected_profiles": []
},
"approver": {
"sub": "696a76674...5548513d3d",
"zoneinfo": "America/New_York",
"locale": "en_US",
"updated_at": "1686659007",
"birthdate": "2005-06-13",
"email": "(someEmail)",
"role_identifier": "(someDefinedRole)",
"registration_role": "(someDefinedRole)",
"approved": true,
"shadow_account": false,
"site_token": "536b632f462b...39773d3d",
"verification_tier": "C",
"user_profile": {
"registration_role": "(someDefinedRole)",
"role_identifier": "(someDefinedRole)",
"site_token": "536b632f46...425839773d3d",
"approved": true,
"shadow_account": false,
"verification_tier": "C",
"attributes": [],
"display_names": []
},
"connected_profiles": []
},
"requestTimestamp": 1688515878205
},
"timestamp": 1688515881653
}
]
}
Partner Site Navigation
Parters that have multiple sites (services) will require their users to navigate between sites. The PRIVO platform must act as the pass-through when navigating between sites. The login widget will handle this pass-through. The target URI is passed in the redirect_uri parameter in the authorization URL used to invoke the login widget. The client_id is for the target site must also be used in the authorization URL.
Example:
https://privohub-int.privo.com/oauth/authorize
?response_type=id_token%20token
&client_id=s6BhdRkqt3-another-site-clientid
&redirect_uri=https://another-site-client.example.org/
&scope=openid%20profile%20user_profile%20additional_info
&state=af0ifjsldkj
&nonce=n-0S6_WzA2Mj