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.

  1. The RP (Client) sends a request to the OpenID Provider (OP).
  2. The OP authenticates the End-User and obtains authorization.
  3. The OP responds with an ID Token and usually an Access Token.
  4. The RP can send a request with the Access Token to the UserInfo Endpoint.
  5. 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:

  1. Client prepares an Authentication Request containing the desired request parameters.
  2. Client sends the request to the Authorization Server.
  3. Authorization Server Authenticates the End-User.
  4. Authorization Server obtains End-User Consent/Authorization.
  5. Authorization Server sends the End-User back to the Client with an Authorization Code.
  6. Client requests a response using the Authorization Code at the Token Endpoint.
  7. Client receives a response that contains an ID Token and Access Token in the response body.
  8. Client validates the ID token and retrieves the End-User's Subject Identifier.

Authorization Code Flow Diagram

Authorization Code Flow Diagram

Implicit Code Flow

The Implicit Flow follows the following steps:

  1. Client prepares an Authentication Request containing the desired request parameters.
  2. Client sends the request to the Authorization Server.
  3. Authorization Server Authenticates the End-User.
  4. Authorization Server obtains End-User Consent/Authorization.
  5. Authorization Server sends the End-User back to the Client with an ID Token and, if requested, an Access Token.
  6. Client validates the ID token and retrieves the End-User's Subject Identifier.

Implicit Code Flow Diagram

Implicit Code Flow Diagram

Hybrid Code Flow

The Hybrid Flow follows the following steps:

  1. Client prepares an Authentication Request containing the desired request parameters.
  2. Client sends the request to the Authorization Server.
  3. Authorization Server Authenticates the End-User.
  4. Authorization Server obtains End-User Consent/Authorization.
  5. 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.
  6. Client requests a response using the Authorization Code at the Token Endpoint.
  7. Client receives a response that contains an ID Token and Access Token in the response body.
  8. Client validates the ID Token and retrieves the End-User's Subject Identifier.

Hybrid Code Flow Diagram

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 be code. 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:

ClaimDefinition
consent_urlURLs for the consent widget.
permissionsFeature permission details.
verification_tierCurrent verification level.
minorIs the User a minor?
role_identifierRole identifier for the User.
approvedHas the User approved their account.
shadow_accountApplies only to children and minors.
site_tokenUnique identifier that associates the current User with a site or service.
activation_timeThe time in seconds when the User's account was approved.
parent_emailThe email address for the current User's parent. Applies only to children.
display_namesArray of displaynames for the User.
consent_requestsConsent requests that are associated to the User.
birthdate_timeUser birth date time in seconds.
registration_roleRole the User registered with.
teenIs the User a teen?
teacherIs the User a teacher?
studentIs the User a student?
pinreturns 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.

ScopeDefinition
PRIVOLOCKUsed for API access.
TRUSTVerifies entity is a trusted partner.
user_profileProvides attributes and display names of the User.
additional_infoProvides permissions, role information, site token, shadow account and verification tier for the User.
trust_emailTrusted partnership to allow email to be flagged as "verified".
Requires use case and contract approval.
delete_accountRemoves accounts entirely from the system.
Requires use case and contract approval.
shadowCreates shadow account User
consent_urlDisplays 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 and Accept: 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:

PropertyRequired?Description
siteIdREQUIREDThe site/service that the User is registering for. Provided by PRIVO administrator.
modeOPTIONALSet to SIMPLE if rendering widget in an iframe, otherwise, do not include in request. SIMPLE mode removes the PRIVO UI header and footer values.
siteTokenOPTIONALProvide this parameter if the desire is to have user redirected to PRIVO Account Settings upon completion of registration. Provided by PRIVO administrator.
redirect_uriREQUIREDThe Client endpoint URL to redirect the User to upon completion of registration.
access_tokenREQUIREDRP 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.

PropertyDescription
serviceIdUnique identifier for the User and is the same as standard claim ‘sub’.
access_tokenThe 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);

Config Members group

PropertyRequiredTypeDescription
apiKeyNostringProvided by PRIVO Admin.
siteIdentifierYesstringProvided by PRIVO Admin.
displayModeYesstringPossible 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

PropertyRequiredTypeDescription
firstNameNostring50 character max
lastNameNostring50 character max
birthDate (DEPRECATED)NolongNumber of milliseconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time
birthDateYYYYMMDDNostringBirth Date in "yyyy-mm-dd" format
emailNostringEmail address of the user who is verifying. 256 character max
postalCodeNostringIf US, numeric-5 postal code
phoneNostringIn the full international format (e.g. "+17024181234")
partnerDefinedUniqueIDNostringUnique 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.

FieldTypeDescription
eventstringThe event for which the callback/webhook pertains to. See Verification Events for possible values.
errorCodeintegerThe result code of the operation when an error occurs. See Error Codes section.
errorMessagestringA short textual description of an error associated with the errorCode for logging purposes. See Error Codes section.
resultobjectThe 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

EventDescription
onVerifyCancelWhen user has canceled the verification.
onVerifyCompleteWhen user has successfully completed the verification process and has been verified.
onVerifyDoneWhen the user has completed the verification and closed the verification widget.
onVerifyErrorIf 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:

PropertyTypeDescription
verifiedbooleanHas the user successfully verified?
transactionIDstringUnique identifier for the transaction. Partner can retain this value for traceability.
verificationMethodNamestringThe verification method chosen by the user. Possible values: CreditCard, DriversLicense, SSN, CorporateEmail, PrintForm, Phone
matchOutcomeNamestringSpecific 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’).
matchCodestringA code that identifies the field groups that are matched in the verification request. May be undefined.
matchValuesobjectKey/value map that contains matched fields and values (for CC or SSN verification). May be undefined.
requestIDstringUnique identifier for the verification request. The Partner should retain this value for traceability.
redirectURLbooleanReturn URL address passed by partner to send the user directly following onVerifyDone event. May be null.
partnerDefinedUniqueIDstringValue passed by Partner in verify_config of the verification request. PRIVO returns this value in the onVerifyComplete event. Can be undefined.
requestTimestamplongTimestamp of the completed verification request.
localestringLocation of the user as defined by their browser settings.
identificationNumberstringUnique 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.
attemptIdstringIdentifier used to notate the attempt request.
messagestringFor 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.

PropertyTypeDescription
VERIFY_ACCOUNTstringWhen 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 CodeError Message
10001Invalid API Key or access_token
10002Missing site_id parameter
10003Unexpected error
10100Invalid email address
10101Misconfigured verification methods

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.

PropertyTypeDescription
VERIFY_OFFLINE_VERIFIEDstringWhen PRIVO Administrator approves an offline verification request.
VERIFY_REMOVEDstringWhen PRIVO Administrator declines an offline verification request.
VERIFY_PURGEDstringWhen 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/#/

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:

FieldTypeDescription
eventstringThe event for which the callback pertains to. See Login Events for possible values.
dataobjectThe 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.

EventDescription
onLoginWhen user has successfully completed the login process.
onLoginErrorIf an error occurs.
onCloseWhen 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:

PropertyTypeDescription
jwtobjectResult of the user authentication represented as a JSON Web Token (JWT)
userinfoobjectUserinfo for the authenticated user
startPlayModebooleanApplicable 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

StatusDescription
UndefinedThe status is returned if it is an initial run
BlockedDepending 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)
MultiUserBlockedUser is blocked according to multi-user activity(can appear only for multi-user integrations)
AgeEstimationBlockUser is blocked according to age estimation step results(can appear only if age estimation is enabled)
AllowedThe status is returned if a user age doesn’t require any verification, depending on partner configuration rules
ConsentRequiredThe 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
ConsentApprovedA user successfully passed the Consent flow (online or offline passed)
ConsentDeniedA user failed passing the Consent flow
AgeVerificationRequiredThe 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
AgeVerifiedA user successfully passed the Age Verification flow
AgeBlockedA user is blocked while passing the Age Verification flow because partner configuration is not satisfied
IdentityVerificationRequiredThe 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
IdentityVerifiedA user successfully passed the Identity Verification flow (online or offline passed)
PendingThe 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
Simple Age Gate Flow Diagram

Age Gate Flow Diagram (with Age Recheck)
Simple Age Gate Flow Diagram

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

StatusDescription
UndefinedThe status is returned if it is an initial run
PendingThe 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
ConfirmedThe status is returned if a parent user verifies age for a child user
DeclinedThe status is returned if a child user is declined by a parent user to access a partner app
CanceledThe 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 to https://id-svc.privo.com on production enviroment or https://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.

PropertyDescription
siteTokenRequired 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:

PropertyIdentifierDescriptionRestrictions/Format
First NamefirstNameRepresents the UserNo spaces, numbers or special characters allowed
Middle InitialmiddleInitialUser's middle initialNo numbers, maximum of 4 characters. Periods allowed
Last NamelastNameUser's given nameNo numbers or special characters allowed
Last InitiallastInitialFirst initial of User's Last NameRarely used. No numbers or special characters, maximum of 1 charcter.
Birth DatebirthDate_YYYYMMDDRepresents age of the UserMust follow format YYYYMMDD
Email AddressemailUser's email addressMust be proper email with @ and .xx(x)
Parental Email AddressparentEmailEmail address of Parent when registering a childMust be proper email with @ and .xx(x)
GendergenderSex of the UserDependent on API may be enumerated as 1,2 OR given as Male, Female
Mobile PhonemobilePhoneUser's cell phone number7 to 10 numbers, no hyphens
Home Phone Numberaddress.homePhoneNumberUser's home phone number7 to 10 numbers, no hyphens
Street Addressaddress.streetAddress1User's street number and nameOpen field
Apartment/Suiteaddress.streetAddress2User's suite or apartment numberOpen field
Cityaddress.cityUser's cityOpen field
State/Provinceaddress.stateProvinceUser's state or province2 letters for US, oopen for INTL.
Regionaddress.regionUser's regionINTL. only. Open field
Postal Codeaddress.postalCodeUser's postal code5 digits for US, open for INTL.
Countryaddress.countryUser's country2 letters e.g. US
Display NamedisplayNameName that displays in a serviceUnique to the given service. Can be used for login to given service
UsernameuserNameUsername used for authenticationUnique 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...)

PropertyIdentifierDescriptionRestrictions/Format
Favorite ColorfavColorUser's favorite colorString for color description
Device IdentifierdeviceIdIdentifier representing a given deviceString
Partner UUIDpartnerUuidUnique identifier representing UserString supplied by Partner
Participation IDparticipationIdUnique identifier representing User's participationString supplied by Partner
Registration PINregistrationPinPIN representing a User in the PRIVO system used to associate multiple adults to a given accountString set by PRIVO
Shirt SizeuserShirtSizeSize of a shirt being provided by Service to UserDrop-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

PropertyTypeDescription
sendParnetEmailBooleanDoes system send consent email to parent email address provided?
sendRegistrationEmailBooleanDoes system send consent email to User email address provided?
sendCongratulationsEmailBooleanDoes system send post registration emails?
roleIdentifierStringIdentifier that defines a given role.
Provided by PRIVO Administrator
emailVerifiedBooleanTrusted partner use to define email verified status.
Requires PRIVO contract approval
minorRegistrationsCollectionAssociated child accounts.
shadowAccountBooleanShuld account start as shadow?
serviceIdStringUnique identifier assigned to User.
childServiceIdStringUnique identifier assigned to child User.
forceActivateBooleanDoes the account get activated immediately?
Requires PRIVO contract approval
featuresCollectionFeature section to define featureIdentifier(s).
featureIdentifierStringIdentifier used to define a given feature(s).
Provided by PRIVO Administrator
attributesCollectionAttribute section to define custom attribute(s).
attributeNameStringUnique name assigned to a custom attribute.
Provided by PRIVO Administrator
valueStringValue assigned to the custom attributeName in use.
requesterServiceIdStringServiceId of the requesting User.
approverServiceIdStringServiceId of the approving Granter.
setValueBooleanUsed for password set/reset API to determine whether set or reset is used.
emailToResolveCollisionStringUsed for password API to resolve conflicts of multiple accounts found.
suggestBooleanUsed for username or display name API to suggest additional names if the one provided is already taken.
verificationTierValueVerification 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:

PropertyRequired?Description
roleIdentifierYESUnique identifier assigned to the PRIVO role (provided by a PRIVO Administrator)
featureIdentifiersNOComma-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:

PropertyRequired?Description
roleIdentifierYESUnique 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:

PropertyRequired?Description
siteIdNOThe ID that identifies the Partner service.
statusNODefines 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:

PropertyRequired?Description
requesterServiceIdYESDefines the given User (Requester) whom is requesting consent, e.g. child serviceId.
approverServiceIdYES, IF...If approver email is not present
Defines the given Granter (Parent) who needs to provide consnet for the User.
emailYES, 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:

  1. Select xcodeproj
  2. Go to File -> Swift Packages -> Add Package Dependency
  3. Use git@github.com:Privo/privo3-ios-sdk.git as repo URL
  4. 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
Simple Age Gate Flow Diagram

Age Gate Flow Diagram (with Age Recheck)
Age Gate Flow Diagram

Age Gate MultiUser Flow Diagram
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

AgeGateEvent

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 with Task.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

AgeGateEvent

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 with Task.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

AgeGateEvent

Parameters

data - instance of type CheckAgeData

Throws

There is only one exception type - PrivoError.

  • PrivoError.cancelled is thrown if cancelled with Task.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

AgeGateEvent

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 with Task.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 CodeError Message
10001Invalid API Key or access_token
10002Missing site_id parameter
10003Unexpected error
10100Invalid email address
10101Misconfigured verification methods
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
}

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:

  1. Open your project in Android Studio
  2. Download the library (using Git, or a zip archive to unzip)
  3. Go to File > Import Module and import the library as a module
  4. Right-click your app in project view and select "Open Module Settings"
  5. Click the "Dependencies" tab and then the '+' button
  6. Select "Module Dependency"
  7. Select "SDK" (not SDK Project)

To verify manual installation, check:

  1. 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.

  1. 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
Simple Age Gate Flow Diagram

Age Gate Flow Diagram (with Age Recheck)
Age Gate Flow Diagram

Age Gate MultiUser Flow Diagram
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 auth module:

  import com.privo.sdk.PrivoVerification

You can instantiate the PRIVO auth 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 CodeError Message
10001Invalid API Key or access_token
10002Missing site_id parameter
10003Unexpected error
10100Invalid email address
10101Misconfigured verification methods

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:
TypeDefinition
* Key/ValuePartner 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 SignedPartner 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 TokenPartner 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:
OutcomeMethodsDescription
PassOffline/OnlineVerification passed. User has successfully verified either by online or offline method and ready to proceed futher
FailOnlineVerification failed. User provided incorrect verification data
PendingOffineVerification submitted for manual review.
DeclinedOffineVerification declined by administrator.
PurgedOffineVerification 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
OutcomeDescription
PENDINGConsent process was started bit not finised
EXPIREDVerification failed. User provided incorrect verification data
DENIEDConsent was rejected by user or his granter
APPROVEDConsent approved by user himself or his granter (depending on a user role/age)
POSTPONEDConsent 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