The Incoming Webhooks connector enables you to capture incoming Webhooks from an external service and update user and account data in Hull by writing Javascript.
Go to the Connectors page of your Hull organization, click the button “Add Connector” and click “Install” on the card.
After installation, open the Code Editor, copy the displayed Webhook Endpoint and paste it in your external service and define how received payloads will be manipulated and sent to Hull.
After installation and configuring the URL to call in the Settings click the Code Editor
button. You will be presented with the three column Dashboard layout.
To populate the input column, you need to have the service emit at least one call to Hull. Each API call, up to 100, will be stored and will be available to select as input.
The Connector allows you to receive data from external systems, write Javascript to transform the received data to update users and accounts in Hull, as well as emit events in Hull to track behavioral data.
Available actions that can be coded: - create users/accounts
- create user/account attributes
- update user/account attributes
- create user events
- link users to accounts
To make the Connector even more powerful, you can use the request
library https://github.com/request/request to call external services.
You can access the request data directly using the following variables:
Variable Name | Description |
---|---|
body | Contains the parsed data as a json object. |
requestBody | Object containing the body that was sent. |
requestHeaders | Object containing HTTP headers. |
responseHeader | Object containing HTTP headers. |
url | The url that was called. |
method | A string describing the HTTP method. |
status | The response status code |
params | Object containing the route parameters of the request. |
Please note that the availability of these variables may be dependent on the external service.
Use the hull.asUser()
function to reference an existing user or create a new one.
Function signature:
hull
.asUser({external_id: <value>, email: <value>})
It is recommended to use the external_id
if your payload contains it or rely on the email
as fallback option. You can pass both identifiers if they are available, but the external_id
will always take precedence over the email
. For the purpose of simplicity, the following code will only show the external_id
identifier.
Set top-level and ungrouped attributes:
hull
.asUser({external_id: <value>})
.traits({ ATTRIBUTE_NAME: <value> })
Set multiple attributes at once by passing in an object of attributes:
hull
.asUser({external_id: <value>})
.traits({ ATTRIBUTE_NAME: <value>, ATTRIBUTE2_NAME: <value> })
By default, non top-level attributes are stored in the traits
attributes group. To group the attributes in a custom group, pass the group name as source in the second parameter:
hull
.asUser({external_id: <value>})
.traits({ ATTRIBUTE_NAME: <value> }, { source: <group_name> })
Or provide the group name per attribute:
hull
.asUser({external_id: <value>})
.traits({ GROUP_NAME/ATTRIBUTE_NAME: <value> })
To delete an attribute:
hull
.asUser({external_id: <value>})
.traits({ ATTRIBUTE_NAME: null })
Use the hull.asAccount
function to reference an existing account or create a new one.
Function signature:
hull.asAccount({external_id: <value>, domain: <value>})
It is recommended to use the external_id
if your payload contains it or rely on the domain
as fallback option. You can pass both identifiers if they are available, but the external_id
will always take precedence over the domain
. For the purpose of simplicity, the following code will only show the external_id
identifier.
Set top-level and ungrouped attributes:
hull
.asAccount({external_id: <value>})
.traits({ ATTRIBUTE_NAME: <value> })
Set multiple attributes at once by passing in an object of attributes:
hull
.asAccount({external_id: <value>})
.traits({ ATTRIBUTE_NAME: <value>, ATTRIBUTE2_NAME: <value> })
By default, non top-level attributes are stored in the traits
attributes group. To group the attributes in a custom group, pass the group name as source in the second parameter:
hull
.asAccount({external_id: <value>})
.traits({ ATTRIBUTE_NAME: <value> }, { source: <group_name> })
Or provide the group name per attribute:
hull
.asAccount({external_id: <value>})
.traits({ GROUP_NAME/ATTRIBUTE_NAME: <value> })
To delete an attribute:
hull
.asAccount({external_id: <value>})
.traits({ ATTRIBUTE_NAME: null })
Use the hull.asUser().track()
function to emit events. Note that there is 10 track call limit per received API call.
Function signature:
hull
.asUser({external_id: <value>})
.track( "<event_name>" , {
PROPERTY_NAME: <value>,
PROPERTY2_NAME: <value>
}, {
ip: "0", //Or the source IP - if present, Event will be geolocated
created_at: "created_at_timestamp", //Defaults to `now()`
event_id: "unique_event_id", //To prevent duplication
referer: "https://referrer.com", //null for Server calls
source: "calendly", //a namespace such as "zendesk", "mailchimp", "stripe"...
type: "meeting"
}
);
The first parameter is a string defining the name of the event while the second parameter is an object that defines the properties of the event.
Use the hull.asUser().account()
function to link users to accounts. Provide the claims, domain
, id
and/or external_id
of the account.
Function signature:
hull
.asUser({external_id: <value>})
.account({ domain: <value> })
You can add or remove anonymous_ids to Users and Accounts with the following syntax:
hull
.asUser({ email: "foo@bar.com" })
.alias({ anonymous_id: "foobar:1234" });
hull
.asUser({ email: "foo@bar.com" })
.unalias ({ anonymous_id: "foobar:1234" });
hull
.asAccount({ domain: "bar.com" })
.alias({ anonymous_id: "foobar:1234" });
hull
.asAccount({ domain: "bar.com" })
.unalias ({ anonymous_id: "foobar:1234" });
The processor exposes several external libraries that can be used:
Variable | Library name |
---|---|
_ | The lodash library. (https://lodash.com/) |
moment | The Moment.js library(https://momentjs.com/) |
urijs | The URI.js library (https://github.com/medialize/URI.js/) |
request (deprecated) | The simplified request client (https://github.com/request/request) |
superagent | The simple and elegant request library (https://github.com/visionmedia/superagent) |
uuid | The uuid library (https://github.com/uuidjs/uuid) |
LibPhoneNumber | The google-LibPhoneNumber library (https://ruimarinho.github.io/google-libphonenumber/) |
Please visit the linked pages for documentation and further information about these third party libraries.
The uuid
library exposes the version 4 of the algorithm, and only accepts the first options
argument - other arguments will be ignored. As a result, here’s the way to use it:
const user_id = uuid()
//or
const user_id = uuid({ random: [ 0x10, 0x91, 0x56, 0xbe, 0xc4, 0xfb, 0xc1, 0xea, 0x71, 0xb4, 0xef, 0xe1, 0x67, 0x1c, 0x58, 0x36, ] });
The LibPhoneNumber
library exposes a subset of the google-libphonenumber
library. Here’s how to use it
//PhoneNumberFormat is the PhoneNumberFormat object from the library;
//PhoneNumberUtil is an INSTANCE of the PhoneNumberUtil methods
const { CountrySourceCode, PhoneNumberType, PhoneNumberFormat, PhoneNumberUtil } = LibPhoneNumber;
const number = PhoneNumberUtil.parseAndKeepRawInput('202-456-1414', 'US');
console.log(number.getCountryCode()); //1
// Print the phone's national number.
console.log(number.getNationalNumber());
// => 2024561414
// Result from isPossibleNumber().
console.log(PhoneNumberUtil.isPossibleNumber(number));
// => true
PhoneNumberUtil
Checkout i18n.phonenumbers.PhoneNumberUtil
: https://ruimarinho.github.io/google-libphonenumber/#google-libphonenumber-methods-i18nphonenumbersphonenumberutil
Calling PhoneNumberUtil.parse("1234-1234")
will return an instance of PhoneNumber
, which has the following methods: https://ruimarinho.github.io/google-libphonenumber/#google-libphonenumber-methods-i18nphonenumbersphonenumber
Checkout the Docs for CountryCodeSource
, PhoneNumberFormat
, PhoneNumberType
which are statics
The request library is now deprecated. Processors using the request library will be still operational, but we advise you to migrate to the super-agent request library which is much more intuitive and elegant to use.
If you are about to write new code to perform any API request, please refer to the Using Superagent section.
The library exposes request-promise
to allow you to call external APIs seamlessly:
const response = await request({
uri: 'https://api.github.com/user/repos',
qs: {
access_token: 'xxxxx xxxxx' // -> uri + '?access_token=xxxxx%20xxxxx'
},
headers: {
'User-Agent': 'Request-Promise'
},
json: true // Automatically parses the JSON string in the response
})
console.log(response)
To perform API requests, the processor connector exposes the superagent library through the superagent
keyword. It is an instance of the original superagent library with additional plugins added behind the scenes to make it run smoothly in your processor code. This comes with some syntax restrictions that our instance of superagent won’t work with, more on that right below.
The exposed superagent instances cannot be called as function, so following code won’t work:
const res = await superagent('GET', 'https://www.foobar.com/search');
Instead always call a method on superagent object choosing which HTTP method you want to use. See examples Below.
Here are a few code snippets to use the super-agent request library in your processor code:
const response = await superagent
.get("https://example.com/foo")
.set("accept", "json") // Set a header variable by using the set() function.
.set(`Authorization: Bearer ${api_key}`)
.send({ // Set a body by using the send() function
body_variable: "something" // and by giving it an object.
})
.query({ // Set a query by using the query() function
orderBy: "asc" // and by giving it an object.
})
You can also perform asynchronous requests by using promises as such:
superagent
.get("https://example.com/foo")
.set("accept", "json")
.set(`Authorization: Bearer ${api_key}`)
.send({
body_variable: "something"
})
.query({
orderBy: "asc"
})
.then(res => {
console.log(res.body);
})
Handling errors is also possible, either by using promises or by wrapping the code in a try catch
statement:
superagent
.get("https://example.com/foo")
.set("accept", "json")
.set(`Authorization: Bearer ${api_key}`)
.then(res => {
console.log(res.body);
})
.catch(err => {
console.log(`Error: ${err}`);
})
try {
const response = await superagent
.get("https://example.com/foo")
.set("accept", "json")
.set(`Authorization: Bearer ${api_key}`);
} catch (err) {
console.log(`Error: ${err}`);
}
You can find full documentation of the superagent library here. Keep in mind that calling superagent as function does not work.
You might have noticed a warning message coming on your processor saying that your code is using a deprecated request library. In order to fix that, you need to replace request
with the superagent library.
There are mostly two things to adjust. First you need to replace your request options object with set of chained methods on superagent instance. Second you will need to look for the response.body
object instead of looking directly at the data
object.
To illustrate that, let’s have a look at a code block using the deprecated request library, and another code block with the result of migrating it.
// Old request library
const reqOpts = {
method: "GET",
uri: "http://www.omdbapi.com/?t=James+Bond"
};
return new Promise((resolve, reject) => {
request(reqOpts, (err, res, data) => {
if (err) {
console.info("Error:", err);
return reject(err);
}
// data contains the response body
if(_.isString(data)) {
data = JSON.parse(data);
}
resolve(data);
});
});
// With super-agent library
return superagent
.get("http://www.omdbapi.com/?t=James+Bond")
.then(res => {
// res.body is parsed response body
return res.body;
})
.catch(err => {
console.info("Error:", err);
})
ls
for lead score, just name it lead_score
._at
or _date
as suffix to your trait name to let hull recognize the values as valid dates. You can pass either "1234"
as the value for trait customerId
, the trait will be always a treated as string, even if you intended it to be a number.track
calls and to plan accordingly when setting a trait to the current timestamp.When operating you might want to log certain information so that it is available for debugging or auditing purposes while other data might be only of interest during development. The processor allows you to do both:
console.log
is used for development purposes only and will display the result in the console of the user interface but doesn’t write into the operational logs.console.info
is used to display the result in the console of the user interface and does also write an operational log.You can access the operational logs via the tab “Logs” in the user interface. The following list explains the various log messages available:
Message | Description |
---|---|
compute.console.info | The manually logged information via console.info . |
compute.user.debug | Logged when the computation of a user is started. |
Learn how Hull unifies and syncs customer data by watching our product tour