Setting Up
Getting Started with Integration
After you are given partnerId
and partnerToken
for Sandbox environment, you are ready to start integrating HAPI Elements into your application.
When your integration is complete with Sandbox environment, we will do some checks mentioned in Releasing to Production to test & verify that your integration, then you will be given different partnerId
and partnerToken
for the Production environment then you will need to switch the URLs from Sandbox Environment to Production Environment.
partnerToken
security best practices
partnerToken
is used to authenticate your users by generating a JWT token, and it should never be exposed in your frontend application. You need to store partnerToken
securely in your backend; we suggest storing it in environment variables of your backend code. Your frontend application then can make a request to your own backend to authenticate the user.
Sandbox Environment
This environment, has the same code and database structure as Production in both HAPI Backend and HAPI Elements but the databases itself is different so that the test data is isolated on Sandbox environment and does not get carried over to the Production environment.
The VONQ Taxonomy data such as Job Functions, Locations, Industries are exactly the same as Production environment so that if you have code that works with id
of such taxonomies, when you switch to Production environment, your code will work as is.
Please note that the data you create/manipulate in Sandbox environment, will not get carried to Production environment as the data on Sandbox environment is merely for testing.
Sandbox Environment URLs
- API URL to generate the JWT token on your backend:
https://marketplace-sandbox.api.vonq.com
- HAPI Elements URL to load Elements via
injector.js
https://elements-sandbox.hapi.vonq.com
Production Environment
For this environment, you should have been given partnerId
and partnerToken
that is different than Sandbox environment, you will use these with the below URLs:
- API URL to generate the JWT token on your backend:
https://marketplace.api.vonq.com
- HAPI Elements URL to load Elements via
injector.js
:https://elements.hapi.vonq.com
It is really important that you keep partnerToken
safe somewhere in your environment variables. partnerToken should never leak to the public!
Sandbox environment is a testing environment thus any money related transaction and any campaign ordered is not actually effective. Money related transactions (such as when topping-up the Wallet) are done using a test credit card and the end user does not receive an invoice.
On Sandbox environment, the campaigns ordered are not sent to the job boards as most job boards do not allow posting "test" campaigns therefore no real campaign is created and the contract's credits are not deducted.
Production Environment
After we do the checks mentioned in Releasing to Production and confirmed that your integration is working, we will ask you to provide us a hash (string or object) to be used as part of JWT HS-256 token generation; then give you new partnerId
and partnerToken
.
You will then change your partnerId
and partnerToken
in your application as well as the URLs from Sandbox to Production URLs.
Production Environment URLs
- API URL to generate the JWT token on your backend:
https://marketplace.api.vonq.com
- HAPI Elements URL to load Elements via
loader.js
:https://elements.hapi.vonq.com
Making a request to authenticate your users
You need the following requisites:
- an API endpoint on your backend to authenticate your users with HAPI Backend
- an API request made from your frontend to your backend to the auth endpoint (optional if you are utilizing
sessions
)
Keep in mind that partnerToken
should never be exposed in your frontend application, please refer to partnerToken
security best practices section for more info.
A request needs to be made on your backend to generate a JWT token for your user. An example is given below for Typescript application using axios
package.
Loading...
The clientToken
should be sent as a response so frontend code can pass them to the injector script to load HAPI Elements.
We recommend getting a new token per user session; session referring to user logging in or reopening their browser, and your application has just been freshly mounted.
Note about JWT Expiry
The JWT token expires every hour; thus, you might need to get a new JWT token each time you want to load HAPI Elements. You can adjust your business logic to refresh the token as needed. HAPI Elements does an automatic token refresh request every 5 minutes after it has been loaded and updates the token internally.
Listening to JWT Token Refresh
To be aware of when token has been refreshed (so that for example you can adjust your Cookies or LocalStorage) you can add an event listener as such:
window.hapiApi.onChangeJWTToken((newToken: string, oldToken: string) => {
// do some logic with the new token like saving to Cookies or LocalStorage
})
Refreshing the JWT Token Manually
To refresh the token manually, you can call the below function:
const tokenResponse = await window.hapiApi.refreshJWTToken()
// New token after refresh
// Please note that, if you added a listener callback via onChangeJWTToken
// your callback will also run
console.log('HAPI Elements JWT Refresh Token Response:', tokenResponse?.token)
Changing Auto JWT Refresh Token Interval in Seconds
By default, HAPI Elements will auto-refresh the JWT token every 5 minutes. To change this timer, you can do:
const intervalInSeconds = 60 * 30 //refresh every thirty minutes
window.hapiApi.setJWTRefreshIntervalInSeconds(intervalInSeconds)
Please note that you cannot specify more than an hour for JWT refresh, trying to set for example 60 * 61
(every 61 minutes) will result in an error saying "Number must be less than 3600".
Validating the JWT Token
You can also check if your token is still valid by making an API request as such:
Loading...
Or you can also check if token is valid, after HAPI Elements has been loaded by using the API submodule:
const isValid = await window.hapiApi.validateJWTToken()
console.log('Is HAPI Elements JWT Token Valid?:', isValid)
Understanding what clientId
refers to
Tenant Based
Your application may have "tenants" in which there are sub-users with different sets of permissions. clientId
refers to the "tenant" so that all the sub-users of the tenant can share the same set of data.
So all the users of a tenant must have the same shared clientId
.
To learn how to manage access control within Elements Widgets, please look at Implementing Access Control.
User Based
If your application is not tenant-based, meaning that you are directly working with recruiters doing the job posting, then the clientId
refers to the "user".
Understanding multi tenancy
When loading HAPI Elements, the clientId
passed is for data separation purposes however you may in some cases want to run conditional logic based on user / tenant type. You are able to do that after you successfully integrate a widget to your page. This method works for every variable/function HAPI Elements provides. Here is how it would look like:
const currentTenant = "tenant-b"
const idOfTenantA = "tenant-a"
const idOfTenantB = "tenant-b"
const themeMap = {
tenantA: {
//...theme object
},
tenantB: {
//...theme object
}
}
// or payment methods example
const enabledPaymentMethodsMap = {
tenantA: [
window.hapi.orderJourney.utils.paymentMethodKeys.purchaseOrder,
],
tenantB: [
window.hapi.orderJourney.utils.paymentMethodKeys.wallet,
window.hapi.orderJourney.utils.paymentMethodKeys.directCharge
],
}
const resetSettings = () => {
window.hapi.theming.state.theme = themeMap[currentTenant]
window.hapi.orderJourney.state.paymentMethodsEnabled = enabledPaymentMethodsMap[currentTenant]
}
const onAppLoad = () => {
resetSettings()
}
const onTenantChange = () => {
resetSettings()
}
Including the HAPI Elements Script
Make sure to include all of HAPI Elements integration related code below at top-level of your application so that these only run once per application load.
Let's first create a setup function that will be used to set up credentials before HAPI Elements is loaded.
const onLoadHapiElementsInjector = (clientToken: string) => {
window.hapiInjector.setConfig("clientToken", clientToken)
window.hapiInjector.inject()
}
Then add an event listener so that we can invoke the above function when the script we will add in the following step, has loaded successfully:
const clientToken = "the JWT token you generated via your backend"
window.addEventListener(
"hapi:load:injector",
() => onLoadHapiElementsInjector(clientToken)
)
Then include injector.js
script in your HTML:
Loading...
Injector will take care of loading HAPI Elements script for you. When HAPI Elements loads, it will fire another event, you can then start using HAPI Elements Javascript API:
const onLoadHapiElements = () => {
console.log('[HAPI] Elements has loaded', window.hapi)
}
window.addEventListener("hapi:load:script", onLoadHapiElements)
Alternatively you can also use window.hapiInjector.inject()
function to detect the load of HAPI Elements, this way, you don't need to listen to hapi:load:script
event:
const onLoadHapiElementsInjector = async (clientToken: string) => {
try {
window.hapiInjector.setConfig("clientToken", clientToken)
// Inject is a Promise that resolves when HAPI Elements script has successfully loaded
await window.hapiInjector.inject()
// You can now start using HAPI Elements Javascript API
console.log('[HAPI] Elements has loaded', window.hapi)
} catch (error) {
console.error(error)
}
}
Programmatic way of adding the script with POST request
injector.js
under the hood prepares the config required by HAPI Elements script to load then via the inject
function on window.hapiInjector
, HAPI Elements script is injected into the DOM.
If you don't want to use injector.js
, you can manually inject loader.js
as follows:
Loading...
If your integration setup & authentication was successful, you should see the widget; otherwise, the widget will hide itself with a big red text explaining your token is invalid and post console errors in the developer tools of your browser.
A note about Content Security Policy (CSP)
In case you have CSP rules in your headers, you would need to add the following as part of the allowed list of hosts:
frame-src 'self' https://*.vonq-aws.com https://*.vonq.com
connect-src 'self' https://*.vonq-aws.com https://*.vonq.com
vonq-aws.com
is used for development/testing environments of VONQ.
If you get the following CSP error;
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src (...). Either the 'unsafe-inline' keyword, a hash ('sha256-vsEPtD4ipuOFerJfbP6WWYepfR9645C8YZOl5YYqaXo='), or a nonce ('nonce-...') is required to enable inline execution.
You should do the following:
- First create a nonce and whitelist it in your meta tags. You can use a hardcoded nonce or generate it every time you want to load HAPI Elements scripts. Modify your meta tags by replacing
NONCE-YOU-GENERATED
with the hardcoded one or the one you actually generated. The generated string can be anything random.
<!--
Assume that the nonce we generated is 'hapi-elements'
then meta tag would have 'nonce-hapi-elements' (including the single quotes)
-->
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' YOUR-OTHER-POLICIES 'nonce-NONCE-YOU-GENERATED'" />
- Add
nonce
attribute to the script tag withinjector.js
<script
defer
type="module"
nonce="NONCE-YOU-GENERATED"
src="https://elements-environment-url.vonq.com/api/injector.js"
></script>
By doing this, you are whitelisting only the injector.js
and loader.js
scripts. When you call window.hapiInjector.inject()
, it injects loader.js
script to your DOM and that will reference the same nonce as the script tag with loader.js
Installing Typescript definitions from NPM to your Typescript application (Optional)
If you have a Typescript application and want to utilize the type definitions, you can do so:
To get started using the types, install the package as a dev dependency:
Using NPM:
npm install @vonq/hapi-elements-types --save-dev
Using Yarn:
yarn add -D @vonq/hapi-elements-types
All the types have been exported in a single file so you can get started using the types as such:
import {CampaignCreateForm} from "@vonq/hapi-elements-types";
const campaignObject: CampaignCreateForm = {} //Typescript will now warn about missing properties
If you are using React with TypeScript, you will most likely need to add typings for web components. Create a global.d.ts
in the root folder of your project then include this file in the include
array of tsconfig.json
. Then place these inside global.d.ts
:
import * as React from "react" //do not remove this
declare global {
namespace JSX {
interface IntrinsicElements {
[key: string]: any
}
}
}
If you get this error:
Module parse failed: Unexpected token (20:7)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| import { ATSUserTokenResponse } from "../ats"
You may need to exclude HAPI Elements from ts-loader
in webpack.config.js
as such:
{
//...rest of the webpack config
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules\/(?!\@vonq\/hapi-elements-types).*/, //changed this so Elements types are not excluded
},
]
}
FAQs
Can we install HAPI Elements via a Javascript Package Manager instead of loading with injector.js
?
We do not have an installable package and have no plans to release such a package. We architected HAPI Elements in a way that works for all frameworks, and we don't want to be in favour of ATSs using React versus any other framework; thus, the architecture with iframes inside web components and the SDK to manage the Elements application does an excellent job of providing the same developer experience.
The goal of HAPI Elements is to be as-maintenance free as possible. The autocomplete behaviour on the browser's developer tools, the optional Typescript definitions package and the Playground in the documentation should be enough to help you implement your business requirements.
We are getting 401 errors with a message saying ‘Authentication Required’ from the APIs; how can we resolve this?
There may be something wrong with your credentials and/or permissions. Please get in touch with your Partner Account Manager to get this investigated.