Skip to content

Instantly share code, notes, and snippets.

@R-Peleg
Last active August 11, 2021 07:19
Show Gist options
  • Save R-Peleg/e724613b1b1b2c36fff045750e04cc89 to your computer and use it in GitHub Desktop.
Save R-Peleg/e724613b1b1b2c36fff045750e04cc89 to your computer and use it in GitHub Desktop.
Veriff basic client for nodejs
// Integration with Veriff's API
import axios, {Method} from 'axios';
import {createHmac} from 'crypto';
import express from 'express';
import bodyParser from 'body-parser';
import http from 'http';
type VeriffStartSessionRequest = {
/**
* default is defined on settings - Callback url to which the
* client is redirected after the verification session is completed
*/
callback?: string;
/** Person to be verified */
person?: {
/** First name */
firstName: string
/** Last name */
lastName: string
/** National identification number */
idNumber: string
/** Gender */
gender?: string
/** (YYYY-MM-DD) Date of birth */
dateOfBirth?: string
};
/** Document of a person to be verified */
document?: {
/** Document number */
number: string;
/** ISO-2 - String Issuing country of the document */
country: string;
/** Document type */
type:
'PASSPORT' |
'ID_CARD' |
'DRIVERS_LICENSE' |
'RESIDENCE_PERMIT';
};
/** Vendor specific data string, max 400 characters long, will be sent back unmodified using webhooks */
vendorData?: string
/**
* (required) - Combined ISO 8601 date and time in UTC
* (YYYY-MM-DDTHH:MM:S+Timezone Offset|Z, i.e., 2018-04-18T11:02:05.261Z)
*/
timestamp: string;
}
type VeriffFailureResponse = {
status: 'fail',
code: string,
message: string
};
type VeriffStartSessionResponse = {
status: 'success',
verification: {
id: string;
url: string;
status: string;
sessionToken: string;
}
} | VeriffFailureResponse;
type VeriffUploadMediaRequest = {
/** (required) Type of a document (face|document-front|document-back) */
context: 'face' | 'document-front' | 'document-back';
/**
* base64 encoded image (png|jpg|jpeg)
* Example data:image/png;base64,R0lGODlhAQABAAAAACw=
*/
content: string;
/**
* (required) Combined ISO 8601 date and time in UTC
* (YYYY-MM-DDTHH:MM:S+Timezone Offset|Z, i.e., 2018-04-18T11:02:05.261Z)
*/
timestamp: string;
}
type VeriffUplaodMediaResponse = {
status: 'success',
image: {
/** Type of an image */
context: string;
/** UUID-v4 String UUID v4 which identifies the image */
id: string;
name: string;
/** Object Timestamp object, deprecated, will return null/None */
timestamp: null;
size: number;
mimetype: string;
url: string;
}
} | VeriffFailureResponse;
type VeriffSubmitResponse = {
status: 'success';
} | VeriffFailureResponse;
export class VeriffClient {
apiPublic: string;
apiSecret: string;
baseUrl: string;
constructor(apiPublic: string, apiSecret: string, baseUrl: string = 'https://stationapi.veriff.com') {
this.apiPublic = apiPublic;
this.apiSecret = apiSecret;
this.baseUrl = baseUrl;
}
private async veriffRequest<ResponseType extends object>
(request: object, method: Method, path: string, sign: boolean = true) : Promise<ResponseType> {
const body = JSON.stringify(request);
const signatrueHeader = sign ? {
'X-HMAC-SIGNATURE': createHmac('sha256', this.apiSecret).update(body, 'utf8').digest('hex')
} : {}
const headers = {
'Content-Type': 'application/json',
'X-AUTH-CLIENT': this.apiPublic,
...signatrueHeader
};
const response = await axios.request({
method,
url: this.baseUrl + path,
headers,
data: body,
validateStatus: () => true,
});
return response.data;
}
startSession(request: VeriffStartSessionRequest) : Promise<VeriffStartSessionResponse> {
return this.veriffRequest({verification: request}, 'POST', '/v1/sessions', false);
}
uploadMedia(sessionId: string, request: VeriffUploadMediaRequest) : Promise<VeriffUplaodMediaResponse> {
return this.veriffRequest({image: request}, 'POST', '/v1/sessions/' + sessionId + '/media');
}
submitSession(sessionId: string) : Promise<VeriffSubmitResponse> {
const request = {verification: {
timestamp: (new Date()).toISOString(),
status: 'submitted'
}};
return this.veriffRequest(request, 'PATCH', '/v1/sessions/' + sessionId);
}
}
type VeriffWebhookDecision = {
status: 'approved' | 'resubmission_requested' | 'declined' | 'expired' | 'abandoned';
verification: {
id: string;
status: 'approved' | 'resubmission_requested' | 'declined' | 'expired' | 'abandoned';
code: 9001 | 9102 | 9103 | 9104;
reason: string;
person: {
firstName: string;
lastName: string;
idNumber: string;
/** (Deprecated) ISO-2 String Citizenship */
citizenship: null;
/** (YYYY-MM-DD) Date of birth */
dateOfBirth: string;
nationality: string;
pepSanctionMatch?: string;
gender: 'M' | 'F' | null;
yearOfBirth: string;
placeOfBirth: string;
};
document: {
number: string;
type: 'PASSPORT' | 'ID_CARD' | 'DRIVERS_LICENSE' | 'RESIDENCE_PERMIT' | 'OTHER';
/** ISO-2 String Document country */
country: string;
validFrom: string;
validUntil: string;
};
additionalVerifiedData?: {
driversLicenseCategory?: {
B: boolean | null;
};
};
vendorData: string;
reasonCode: number;
decisionTime: string;
acceptanceTime: string;
riskLabels: {
label: string;
category: 'client_data_mismatch' | 'crosslinks' | 'device' | 'document'
| 'images' | 'network' | 'session' | 'person';
}[]
};
technicalData: {
ip: string;
}
};
type VeriffWebhookEvent = {
/** UUID v4 which identifies the verification session */
id: string;
/** UUID v4 which identifies session attempt */
attemptId: string;
/** Feature on which the event was triggered (selfid refers to the end user flow) */
feature: string;
/** Event code (one of 7001, 7002) */
code: string;
/** Corresponding action description */
action: 'started' | 'submitted';
/** Vendor specific data string set during session creation */
vendorData?: string;
};
/** Create an Express application for */
export function createWebhooksApp(
privateApiKey: string,
onDecision: (decision: VeriffWebhookDecision) => void,
onEvent: (event: VeriffWebhookEvent) => void
) : express.Express {
const app = express();
app.use(bodyParser.text({'type': '*/*'}));
// Verify signature to ensure nobody tries to mess with us
app.use((req, res, next) => {
const expectedSignature = createHmac('sha256', privateApiKey).update(req.body, 'utf8').digest('hex');
const signature = req.headers['x-hmac-signature'];
if (expectedSignature !== signature) {
console.warn('Invalid auth, expected ' + expectedSignature);
res.status(400);
res.send('Invalid authentication');
return;
}
// Parse JSON for application
if (req.headers['content-type'] === 'application/json') {
req.body = JSON.parse(req.body);
}
next();
});
app.post('/decision', (req, res) => {
onDecision(req.body);
res.send({});
});
app.post('/event', (req, res) => {
onEvent(req.body);
res.send({});
});
return app;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment