Register webhook URL
What is a webhook anyway?
Simply put, a webhook URL is like an address wherein you tell us where to find you in case we have some data for you. It's how applications can "speak" to each other and share information.
There are two way apps generally communicate with each other: polling or webhooks. Let's say you order something for yourself from an online e-commerce site. Polling is like going to that e-commerce company's warehouse everyday and asking them if your package is available yet. On the other hand, webhooks are like someone coming to your doorstep as soon as your package ready and delivering it to you. Webhooks eliminate the need for polling. You don't have to waste resources everyday going to them - they immediately come to you as soon it's ready!
Technically speaking, webhooks are automated messages when something happens. They have a payload (a message) and get delivered to an URL. They are an API route on your server to which we can send POST
requests to.
Borrowing from the example above, you might set up a webhook URL on your server like https://yourapp.com/order
to which the e-commerce website will send requests when they're ready to send your package. In your application, the handler for this endpoint might notify you every time it gets called. So, every time your endpoint gets called by the e-commerce site, you know you've got a new package!
Instead of you going to the e-commerce site every hour to check if your package is ready (aka polling), using a webhook immediately lets you know when its ready! Webhooks are like push notifications for your apps. They don't have to check for new info anymore. Instead, when something happens, they can push the data to each other and not waste their time checking and waiting. It's a simple, one-to-one connection that runs automatically.
Set up Webhooks for Knit Syncs
Knit uses webhooks to send you events in data syncs. For example, in the employee sync for an HRIS app, we might you information about new employees added, employees modified etc.
The very first thing you'd need to do is expose a public API route to receive webhook requests from Knit. This route should only accept POST
requests.
The webhook handler needs to handle 5 Knit Events:
record.new
When a new record is encountered eg. a new employee has been added in the HRIS system, or a new application or job has been created in an ATS system
{
"eventId": "e_123sadasdasda",
"eventType": "record.new",
"eventData": {
// the models that you have subscribed to
},
"syncType": "initial_sync",
"syncDataType": "employee/ats_applications/ats_jobs",
"syncJobId": "sj_asda32424",
"syncRunId": "sr_jhhkk80869",
"recordId": "123",
"triggeredAt": "1676292064383"
}
record.modified
When an existing record has been modified eg. an employee's details have been updated in the HRIS system, or an application or job has been modified in the ATS system
{
"eventId": "e_123sadasdasda",
"eventType": "record.modified",
"eventData": {
// the models that you have subscribed to
},
"syncType": "delta_sync",
"syncDataType": "employee/ats_applications/ats_jobs",
"syncJobId": "sj_asda32424",
"syncRunId": "sr_jhhkk80869",
"recordId": "123",
"triggeredAt": "1676292064383"
}
record.deleted
When an exiting record has been deleted or marked as inactive in the application eg. an employee has been marked as inactive in the HRIS system, or an application or job has been deleted in the ATS system
{
"eventId": "ev_jLPn5iJNhvhDBh91xtb8kc",
"eventData": {
// information about which record was deleted
},
"eventType": "record.deleted",
"syncType": "delta_sync",
"syncDataType": "employee/ats_applications/ats_jobs",
"syncJobId": "sj_asda32424",
"syncRunId": "sr_jhhkk80869",
"recordId": "123",
"triggeredAt": "1676292064383"
}
sync.events.processed
You can use this event to audit when a sync has taken place, and the number of records processed
{
"eventId": "ev_UFrXyD8d5yneJ6UnfxAoJ7",
"eventData": {
"status": "processed",
"processed": 200,
"emitted": 0,
"startedAt": "1676292064383",
"processedAt": "1676292068616",
"complete": false
},
"eventType": "sync.events.processed",
"syncType": "delta_sync",
"syncDataType": "employee/ats_applications/ats_jobs",
"syncJobId": "sj_rJWQawZYVS8IqgtXkSJ8BQ",
"syncRunId": "sn_agQO87vjVs6tMe9CEsPkBk",
"triggeredAt": "1676292064383"
}
sync.events.allConsumed
This event is issued when Knit has finished the sync and all events dispatched have been acknowledged. You can use this event to start any downstream processes.
{
"eventId": "ev_Z9s8E9mwdwju7dpRcCLh7d",
"eventData": {
"status": "completed",
"processed": 200,
"emitted": 200,
"consumed": 200,
"completedAt": "1676281276296",
"startedAt": "1676281265551",
"processedAt": "1676281275876",
"complete": true
},
"eventType": "sync.events.allConsumed",
"syncType": "initial_sync",
"syncDataType": "employee/ats_applications/ats_jobs",
"syncJobId": "sj_rJWQawZYVS8IqgtXkSJ8BQ",
"syncRunId": "sn_NHXHC2Hn6oOaLrCfsaqGOM",
"triggeredAt": "1676292064383"
}
sync.heartbeat
This event gives you a continuous heartbeat of the sync process and can be used to track the sync progress.
{
"eventId": "ev_lyrE73Wqx2OilA6amgnQqm",
"eventType": "sync.heartbeat",
"syncType": "initial_sync",
"syncDataType": "employee",
"syncJobId": "sj_iPDl9r96ncVIi7cWRhxZ3n",
"syncRunId": "sr_CuY91HVWAgygQHWTdP1iM1",
"triggeredAt": 1700118676305,
"eventData": {
"processed": "0",
"numRestarts": 3
}
}
The
processed
counter insync.heartbeat
is a tentative counterThe
processed
counter is a tentative counter that indicates how many records have been fetched and processed till now, and is updated periodically.You should rely on the
sync.events.processed
event and theprocessed
andemitted
counters in that to know the accurate count of the number of records processed and emitted at the end of the sync.
sync.failed
This event notifies you that a sync run has failed due to some issue.
{
"eventId": "ev_EJusr0Fb39mtxqGgaI3SES",
"eventType": "sync.failed",
"syncType": "initial_sync",
"syncDataType": "employee",
"syncJobId": "sj_iPDl9r96ncVIi7cWRhxZ3n",
"syncRunId": "sr_CuY91HVWAgygQHWTdP1iM1",
"triggeredAt": 1700118766679,
"eventData": {
"failedAt": 1700118766679
}
}
You can refer to the Logs and Issues pages on the Knit Dashboard for more information.
Use
X-Knit-Integration-Id
andX-Knit-Signature
headers in the webhookEvery webhook will have an
X-Knit-Integration-Id
header which would be the integration-id of the integrated account. You can use this header to identify the account to which the webhook payload belongs.Similarly, webhooks also have have an
X-Knit-Signature
header that an be used to authenticate the incoming request. Do look at Authenticating Knit Webhooks.
Here's some sample code that shows how to create your API Handler for Knit sync events.
//Webhook Handler for Knit Sync, if your server is hosted on https://call.me domain, this api route would be https://call.me/knithook
//You will always receive one employee data as event body on this webhook.
post("/knithook", (req, res) -> {
String apiKey = "Your API Key";
// The integration id for which this sync has been set up.
String integrationId = req.headers("X-Knit-Integration-Id");
String actualSignature = req.headers("X-Knit-Signature");
String reqBody = req.body();
String expectedSignature = getSignature("Your API Key", reqBody);
//always validate the signature received in request header.
if (actualSignature == null ||
actualSignature.isEmpty() ||
!actualSignature.equals(expectedSignature)) {
//block malicious request
res.status(401);
return "not ok";
}
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(reqBody);
String eventType = jsonNode.get("eventType").asText();
switch (eventType) {
case "record.new":
// store this record in db and do some downstream processing,
// like sending a welcome message.
break;
case "record.modified":
// store this record in db and do some downstream processing
// as per your business rules
break;
case "record.deleted":
// deprovision this employee or application from your system or handle this event as per your business rules
break;
case "sync.events.processed":
// use this event to keep track of when syncs happen on Knit
break;
case "sync.events.allConsumed":
// use this to start some downstream process once all the data has been synced,
// like find department wise employee spread, or create org tree for the entire organizatio
// or calculate job wise analytics etc.
break;
}
// Only 200 status marks consumption of an event,
// else Knit will retry event delivery
res.status(200);
return "ok";
})
public static String getSignature(String key, String data) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
val encoded = Base64
.getUrlEncoder().withoutPadding()
.encode(sha256_HMAC.doFinal(data.getBytes(Charsets.UTF_8)))
return encoded;
}
The webhook will always receive one record data in one request call.
If, for example, you are syncing with some account that has 500 employees, you can expect the webhook to be invoked 500 times with each employee data dispatched individually by Knit. All 500 employees data will not be dispatched together on one API call.
Your webhooks should return a status code of 200. If your webhook, for some reason, does not return a 200 status code, we will retry sending you the event every 30 mins (upto 4 days).
Now that I've created my webhook URL, where do I register it?
After you've configured your webhook handler, make sure to publicly expose this route, and use this URL for the step 2 of the getting started journey.
In this step, you’ll be able to register the webhook wherein we’ll send you sync events. While registering the URL, you can also see the different kinds of events that we’ll be sending in different circumstances and test your webhook against them.
Once you’ve successfully registered the webhook, you can then enable the category to start integrating with apps.
Updated about 1 year ago