Using the Token of Trust Verify Person API and Webhooks
Summary
Token of Trust offers two powerful tools for enhancing your application's identity verification capabilities: the Verify Person API and the Webhooks API.
Verify Person API:
Enables real-time identity verification, allowing your application to seamlessly verify users' identities during onboarding or other relevant processes.
Webhooks API:
Provides real-time updates on verification status changes, allowing your application to stay informed about users' verification progress.
Verify Person API
Overview of the Workflow
1. Collect Required Data (On the Client): Gather necessary information such as name and email.
const person = { givenName: firstName, familyName: lastName, primaryEmail: email, }; const clientVerifyPersonBody = { person: person, };
2. Call verifyPerson (From the Client): Make a request to your server's verifyPerson
endpoint.
If you’re running a client webpage or webview you will need to bounce a request off your server - mainly this is bc you cannot reveal the tot secret key to the client, however this function is generally a relatively simple passthrough.
Assuming you are creating a webview you will have to create an endpoint on your server to service requests from your web pages. An example of this is below - where ‘yourVerifyPersonUrl’ would be replaced with your url.
$.ajax({ url: yourVerifyPersonUrl, type: "POST", data: clientVerifyPersonBody, dataType: "json", timeout: 10 * 1000, success: function (content, textStatus, theXhr) { // Handle success }, error: function (theXhr, textStatus, errorThrown) { // Handle error }, });
3. Bundle verifyPerson Data (On Your Server): Assemble the person data for the next step.
Next you need to handle the incoming ajax request - by assembling the person data from any combination (this depends upon your use case) from the web page or better your backend. This allows you to put it into an outbound request for the next step.
Here’s what the payload needs to look like for the verifyPerson ajax request (which is further below).
const personFromClient = (body && body.person); const person = personFromClient || { givenName: firstName, familyName: lastName, primaryEmail: email, }; const verifyPersonBody = { appDomain, // your product app domain (e.g. ‘example.com’). totApiKey, totSecretKey, appUserid: getAppUserid(), // you define - unique user constant. person: person, };
4. Call tot/person Endpoint (From Your Server): Initiate the verification process by calling the tot/person
endpoint.
Using the verifyPersonBody assembled here above - call the tot/person endpoint. Here’s a snippet of what that might look like:
await request({ method: "POST", uri: "https://" + totEndpoint + "/api/person", body: verifyPersonBody, json: true, // Automatically stringifies the body to JSON }) .then(function (response) { return processResponse(response); }) .catch(function (err) { return processResponse(err.response && err.response.body); }); }
Some things to note:
totEndpoint - IMPORTANT - this is either app.tokenoftrust.com or sandbox.tokenoftrust.com endpoint depending upon whether you’re live or testing.
5. Handle Response (On Your Server): Process the response from tot/person
and determine if further verification steps are needed.
When TOT is called - check to see if we have already verified or whether they’ve already cleared. If TOT determines the configured workflow conditions have been met (code ‘conditionsMet’) then verification is not required.
Otherwise a ‘continuation’ is returned. A continuation is a link to a tot workflow that allows the user to interact with Token of Trust to start or continue the verification process.
async function processResponse(response) { var content = (response && response.content) || {}; if (content.code === "WorkflowService:conditionsMet") { return jsonResponse(ctx, { verificationRequired: false }); } else { return jsonResponse(ctx, { verificationRequired: true, continuation: content.continuation, }); } }
6. (On Your Server) Pass along the response from tot/person.
Unless you need to do something on the server side with the response you can generally pass the response along to the client. Here’s an example where we strip the response down to the essentials:
async function processResponse(response) { // console.log(JSON.stringify(response)); var content = (response && response.content) || {}; if (content.code === "WorkflowService:conditionsMet") { return jsonResponse(ctx, { verificationRequired: false }); } else { return jsonResponse(ctx, { verificationRequired: true, continuation: content.continuation, }); } }
7. (On the Client) Display the continuation if present.
In the case where Token of Trust verification needs user involvement (5 above responds with a continuation) then you’ll need to open our modal to continue the process. Here’s how that works:
First you’ll want to add this script to your site on any page (on load) where you might want to display our continuations or widgets:
function setupTokenOfTrust(bindOptions) { // The code below needs to be run on page load. var endpoint = "https://" + bindOptions.host + "/embed/embed.js"; (function (d) { var b = window, a = document; b.tot = b.tot || function () { (b.tot.q = b.tot.q || []).push(arguments); }; var c = a.getElementsByTagName("script")[0]; a.getElementById("tot-embed") || ((a = a.createElement("script")), (a.id = "tot-embed"), (a.async = 1), (a.src = d), c.parentNode.insertBefore(a, c)); })(endpoint); tot("setPublicKey", bindOptions.apiKey || window.totOpts["apiKey"]); }
If verification is required on the client side you can use the continuation object (from 5 above) and pass it straight into tot to handle like this:
function displayContinuation(continuation) { continuation.disableClose = true; tot("modalOpen", "continue", continuation); }
The first line above prevents the modal from being closed which is the behavior most apps want.
Once you call “modalOpen” - you will see a popup over your app (on mobile we simply appear above your app until done) and then once verification is complete control is seamlessly returned back to your app:
This will create an iframe and pop up a dialog to allow the end user to continue the verification process. Above we’re forcing the dialog to remain open until the user finishes the verification process - it’s optional but may make sense for you.
8. (On the Client) Handle Modal Close event
Once the end user is done - the modal will close and control will return to your app. To be informed of this you will probably want to register for this event so you can take action. Here’s an example that’s done:
tot("bind", "modalClose", function (eventData, evt) { verifyPersonInProgress = false; if (evt && evt.data) { var eventData = {}; try { eventData = JSON.parse(evt.data); } catch (err) { return false; } eventData = eventData.data || eventData; if (eventData.continue) { if (lastEvent.eventTimestamp === evt.timeStamp) { return; // don't process invalid data, nor the same event twice. } lastEvent.eventTimestamp = evt.timeStamp; checkVerificationStatusAndDoSomething(); // your function. } } return false; });
Code Samples
Please note these are untested examples drawn and simplified from working production code. That these code samples do require the tot snippet installed and reference some functions in the Utility Functions defined in the appendix.
1. Ajax request (verifyPerson) from the client side.
This assumes that you have assembled the verifyPersonBody (see example above).
function doVerifyPerson(verifyPersonBody, autoOpen, onVerified, onError) { var verifyPerson = window.totOpts["verifyPerson"] || {}; var verifyPersonUrl = verifyPerson.url || window.totOpts["verifyPersonUrl"]; if (verifyPersonInProgress) { return; } verifyPersonInProgress = true; $.ajax({ url: verifyPersonUrl, type: "POST", data: verifyPersonBody, dataType: "json", timeout: 10 * 1000, success: function (content, textStatus, theXhr) { var continuation = content && content.continuation || content; var continuationParams = content.continuation && content.continuation.params || {}; var verificationMessageCode, verificationRequired; if (content && (content.verificationRequired === undefined || content.verificationMessageCode === undefined) && content.code === "WorkflowService:conditionsMet") { const gates = content.details && content.details.conditionResult && content.details.conditionResult.gates; const isCleared = reasonIsPositive(gates["isCleared"]); const isNotRequired = reasonIsPositive( gates["isNotRequired"] ); const isSubmitted = reasonIsPositive(gates["isSubmitted"]); const isUpdateRequested = reasonIsPositive( gates["isUpdateRequested"] ); verificationRequired = false; if (isNotRequired) { verificationMessageCode = undefined; } else if (isCleared) { verificationMessageCode = "isCleared"; } else if (isUpdateRequested) { verificationMessageCode = "isUpdateRequested"; verificationRequired = true; console.error('TOT: conditionsMet and update requested!', content); } else if (isSubmitted) { verificationMessageCode = "isSubmitted"; } } if (verificationRequired !== false && continuationParams.url) { if (autoOpen) { // When required - use continuationParams to start the verification process using the tot function. continuationParams.disableClose = true; tot("modalOpen", "continue", continuationParams); } } else if (verificationMessageCode && verificationMessageCode === "error") { verifyPersonInProgress = false; return onError(content.errorMessage); } else { verifyPersonInProgress = false; return onVerified(content); } }, error: function (theXhr, textStatus, errorThrown) { console.error("Failure(%s) in POST to %s", textStatus, verifyPersonUrl); verifyPersonInProgress = false; }, }); }
2. Server side example of ‘verifyPerson’ endpoint as implemented in koa / node.js
async function verifyPerson(options) { const { apiClient } = options; const appDomain = apiClient.getAppDomain(); const person = { givenName: address.first_name, familyName: address.last_name, primaryEmail: order.email, }; // Person - advanced / optional parameters: // location: buildLocation(address), // shippingLocation: buildLocation(order.shipping_address), // billingLocation: buildLocation(order.billing_address), // orderNumber: order.name, const verifyPersonBody = { appDomain, totApiKey: apiClient.getApiKey(), totSecretKey: apiClient.getSecretKey(), appUserid: utils.hashFromString( order.email.toLowerCase() + "-" + apiClient.getAppId(), "SHA3-256", "hex" ), person: person, }; // Additional optional/advanced parameters: // guest: true, // appTransactionId, // appReservationToken: reservationToken, // traceId: (cart && cart.token) || checkout.cartToken, // browserFingerprint: fingerPrint, var vpOptions = { method: "POST", uri: "https://" + totEndpoint + "/api/person", body: verifyPersonBody, json: true, // Automatically stringifies the body to JSON }; async function processResponse(response) { // console.log(JSON.stringify(response)); var content = (response && response.content) || {}; if (content.code === "WorkflowService:conditionsMet") { return jsonResponse(ctx, { verificationRequired: false }); } else { return jsonResponse(ctx, { verificationRequired: true, continuation: content.continuation, }); } } await request(vpOptions) .then(function (response) { return processResponse(response); }) .catch(function (err) { const body = err.response && err.response.body; return processResponse(body); }); }
Verify Person API Appendix
Utility Functions
We use a number of utility functions above.
// Some utility classes... function reasonIsPositive(reason) { var theReasonValue = reasonValue(reason); return !!(theReasonValue && theReasonValue !== 'false' && theReasonValue !== 'noMatch' && theReasonValue !== 'no_match' && theReasonValue !== 'insufficientData' && theReasonValue !== 'notApplicable'); } function handleNotApplicable(value) { return (value === 'notApplicable') ? undefined : value; } function reasonValue(reason) { var retVal = reason; if (isBoolean(reason)) { retVal = reason; } else if (isObject(reason)) { retVal = reason.value !== undefined ? reason.value : reason.status; } return handleNotApplicable(retVal); } function isObject(possibleObject) { return (possibleObject !== undefined) && Object.prototype.toString.call(possibleObject) === '[object Object]'; } function isBoolean(possibleBool) { return (possibleBool !== undefined) && Object.prototype.toString.call(possibleBool) === '[object Boolean]'; }
Open API 3.0 Yaml
Here’s an Open API Yaml that you should be able to use with most tooling.
openapi: "3.0.0" info: version: "1.0.0" title: "Verify Person API" description: "Given information supplied in the api call - verify that a person is who they say they are. " paths: /person: post: summary: "Verify a person or respond with next steps to verify." operationId: "verifyPerson" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/VerifyPersonOptions" responses: "200": content: application/json: schema: type: object components: schemas: VerifyPersonOptions: type: object required: - person additionalProperties: false properties: appDomain: type: string appTransactionId: type: string appReservationId: type: string appReservationToken: type: string traceId: type: string browserFingerprint: type: string reservation: type: boolean guest: type: boolean appTransactionTags: type: array items: type: string maxLength: 32 appUserid: type: string person: $ref: "#/components/schemas/Person" totAccessToken: type: string totSecretKey: type: string totApiKey: type: string Person: type: object additionalProperties: false properties: dateOfBirth: oneOf: - type: string minLength: 8 maxLength: 64 - type: object properties: year: type: integer month: type: integer day: type: integer location: $ref: "#/components/schemas/Location" shippingLocation: $ref: "#/components/schemas/Location" billingLocation: $ref: "#/components/schemas/Location" phoneNumber: type: string email: type: string format: email primaryEmail: type: string format: email fullName: type: string givenName: type: string familyName: type: string middleName: type: string custom: type: string orderNumber: type: string Location: type: object additionalProperties: false properties: locality: type: string regionCode: type: string region: type: string countryName: type: string countryCode: type: string postalCode: type: string line1: type: string line2: type: string longitude: type: string latitude: type: string
Webhooks API
Token of Trust Webhooks API allows your application to receive real-time updates about verification status changes.
This document covers webhooks and is broken up into a couple parts. The first part is what you’re reading and it describes the feature, how it works and what you’ll receive.
Happy Webhooking and please don’t be afraid to contact us at success@tokenoftrust.com if you run into any trouble.
Configure Webhooks
Webhooks need to be enabled and configured for your Token of Trust account. Reach out to success@tokenoftrust.com to request webhook activation and provide the endpoints for your test and live environments.
Receive Webhook Notifications
Once configured, Token of Trust will send HTTP POST requests to the provided webhook endpoints whenever there's a change in the verification status of a user. These notifications contain information about the verification, including gates, reasons, and timestamps.
Verify HMAC Signatures
For security, every webhook request is signed with an HMAC digest. Ensure that you verify incoming requests by regenerating the HMAC using your webhook secret key and comparing it against the Authorization header. Requests with invalid signatures should be discarded.
Webhook Operations Supported
- isCleared
: Verification has cleared based on your configuration.
- isUpdateRequested
: Reviewer has requested updated information about the user.
- isSubmitted
: User has submitted all documentation and is waiting for a review.
- isRejected
: User has failed to meet the requirements to get verified based on your configuration.
Implementation Details
- Each API key has its own set of configurable webhooks.
- Webhooks support skinny updates, providing only the appUserid and non-personally identifiable information to ensure privacy in case of a breach.
- Notifications are for a single 'update' to indicate that a verification has been updated.
Webhook Structure
The webhook notification is a POST request with a JSON body containing the following fields:
- app_user_id
: Verification's appUserId
- app_transaction_id
: Transaction ID for the verification (if applicable)
- operation
: Type of change experienced
- gates
: List of gates and their values for the verification
- reasons
: List of reasons and their values for the verification
HMAC Signatures
Every webhook request is signed with an HMAC digest and set into the ‘Authorization’ http header. Doing this both protects the request from being tampered with and ensures that the request came from Token of Trust.
Here’s the code used to generate the HMAC in node.js. Integrators should verify all incoming requests by regenerating them with their webhookSecretKey and comparing them against the contents of the Authorization header. Any requests that do not match should be discarded.
/** * Takes the webhook body and creates a hmac based on the secret key. * @param {string} body - The returned body. * @param {string} webhookSecretKey - The current webhook secret access key. * @returns {string} hmac - the hmac generated. */ function generateSignature(body, webookSecretKey) { var hmac = crypto.createHmac('sha1', webookSecretKey); hmac.update(body, 'utf8'); return hmac.digest('base64'); }
Recommendations for Processing and Storage
First and foremost we ask that you follow best practices related to web hooks:
- Dispatch our post requests quickly - saving the authorization header body.
- Close the request to free resources.
- Then process the hook - i.e. check the signature, make database requests, etc.
Testing Webhooks
Unit test and integration test your webhooks.
Use tools like Curl to test webhook endpoints.
Contact Token of Trust support if you encounter issues with webhook notifications.
Webhooks FAQ
How do I start receiving webhooks?
Webhooks are turned on by request. Contact success@tokenoftrust.com to request webhook activation.
How do I check the webhook signature?
Check the signature using provided code samples for Node.js and PHP.
Is it possible to have the Token of Trust server send a test webhook request?
Yes, use the API endpoint api/webhooks/test to simulate webhook requests for testing purposes.
This combined integration of the Verify Person API and Webhooks API provides a robust solution for secure and seamless identity verification within your application. For further assistance or specific questions, feel free to contact Token of Trust support. Happy integrating!