Get notified about updates
A webhook is an HTTP request used to provide notifications to your System when changes happen in Dotfile.
Dotfile sends webhooks to your server to notify programmatically about:
- Case lifecycle
- Case created
- Case updated
- Case status updated
- Case flags updated
- Case contact has actions updated
- Case reviewer has actions updated
- Case info updated
- Case template updated
- Case risk updated
- Case metadata updated
- Case tags updated
- Case review updated
- Case review due
- Case review confirmed
- Case deleted
- Case report lifecycle
- Case report generated
- Note lifecycle
- Note created
- Note updated
- Note deleted
- Note comment lifecycle
- Note comment created
- Note comment updated
- Note comment deleted
- Individual lifecycle
- Individual created
- Individual updated
- Individual info updated
- Individual marked as relevant
- Individual marked as not relevant
- Individual deleted
- Company lifecycle
- Company created
- Company updated
- Company info updated
- Company marked as relevant
- Company marked as not relevant
- Company deleted
- Check lifecycle
- Check started
- Check review needed
- Check approved or rejected
- Check expired
- Check deleted
- Document order lifecycle
- Document order completed
- Document order failed (i.e. document not available)
Configuring webhooks
To receive webhooks, set up a dedicated endpoint on your server and then set up webhook in Dotfile using our API. Dotfile sends POST requests with a raw JSON payload to your designated endpoint.
To create a webhook using the API, use the Create a webhook operation.
Events
Case events
Case.Created
: When a new case is created (empty case).Case.Updated
: When any case property (such asname
,external_id
,status
,flags
,risk
,tags
,template_id
,metadata
,custom_properties
) is updated. Asub_event
is always provided inCase.Updated
events and can be valued to any of the following events :Case.StatusUpdated
: When case status is updated from one status to another one.
Example: caseopen
โapproved
Case.FlagsUpdated
: When case flags are updated (any flag changes can trigger this event).Case.ContactHasActionsUpdated
: Whencontact_has_actions
is updated.Case.ReviewerHasActionsUpdated
: Whenreviewer_has_actions
is updated.Case.InfoUpdated
: When case info are updated (such asname
,external_id
orcustom_properties
).Case.TemplateUpdated
: When case template is updated.Case.RiskUpdated
: When case current Risk is updated.Case.MetadataUpdated
: When case metadata are updated.Case.TagsUpdated
: When tags have been updated on case.
Case.ReviewUpdated
: When the case periodic review is updated (for example when the caseโs risk changes and thenext_review_at
date is updated).Case.ReviewDue
: When the case periodic review is due.Case.ReviewConfirmed
: When the case periodic review is confirmed.Case.Deleted
: When a case is deleted.
Case report events
CaseReport.Generated
: When a case report link is generated.
Note events
Note.Created
: When a new note is created.Note.Updated
: When any note content is updated.Note.Deleted
: When a note is deleted.
Note comment events
NoteComment.Created
: When a new note comment is created.NoteComment.Updated
: When any note comment content is updated.NoteComment.Deleted
: When a note comment is deleted.
Individual events
Individual.Created
: When a new individual is created.Individual.Updated
: When any individual property (not check related) is updated.
Example:risk
property is updated. Asub_event
is always provided inIndividual.Updated
events and can be valued to any of the following events :
-Individual.InfoUpdated
: When individual properties are updated
-Individual.MarkAsRelevant
: When any irrelevant individual is marked as relevant
-Individual.MarkAsNotRelevant
: When any relevant individual is marked as not relevantIndividual.Deleted
: When a individual is deleted.
Company events
Company.Created
: When a new company is created.Company.Updated
: When any company property (not check related) is updated.
Example:risk
property is updated. Asub_event
is always provided inCompany.Updated
events and can be valued to any of the following events :
-Company.InfoUpdated
: When company properties are updated
-Company.MarkAsRelevant
: When any irrelevant company is marked as relevant
-Company.MarkAsNotRelevant
: When any relevant company is marked as not relevantCompany.Deleted
: When a company is deleted.
Check events
Check.Started
: When a check is started on an entity.
Example: Start an AML Check on Individual.Check.ReviewNeeded
: When a check needs manual review.
Example: Found hits on AML Check, a reviewer needs to look at the hits.Check.Approved
: When a check is approved.
Example: Ignore all hits (false-positive), the reviewer approves the AML check which trigger the webhook on this event.Check.Rejected
: When a check is rejected.
Example: A document is invalid, the reviewer rejects the Document check which trigger the webhook on this event.Check.Expired
: When a check is expired.
Example: A document check expiration date has been past, the check become expired.Check.Deleted
: When a check is deleted.
Example: A document check has been removed by a user.
Document order events
DocumentOrder.Completed
: When a document order for a company has been completed .
Example: KBIS for a french company has been retrieved.DocumentOrder.Failed
: When a document order for a company has failed.
Example: Unavailable annual accounts for a company.
Deleted
events does not trigger the deleted event for sub-entity.Example: Case deletion will trigger a
Case.Deleted
event, but noIndividual.Deleted
,Company.Deleted
norCheck.Deleted
event.
Events can have sub events for fine-grain filtering.
For instance, the top-level event
Case.Updated
has one sub-eventCase.StatusUpdated
that allow you to subscribe specifically to the update of the casestatus
and ignore update of other properties.You should note that the top-level event is always trigger and you donโt need to subscribe to a sub-event if you are already subscribe to the top-level event in the same webhook.
Main Event vs. Sub event
For example, if you subscribe to the event Case.Updated
, the event structure is as follows:
- Main Event:
Case.Updated
- Sub event:
Case.StatusUpdated
(for example for can be any otherCase.*Updated
sub event)
You can also subscribe specifically to a sub event such as Case.StatusUpdated
, then the event structure will be as follows:
- Main Event:
Case.StatusUpdated
- Sub event:
None
Best Practices
- Fine-grained control: Subscribe to specific events like
Case.StatusUpdated
if you only need information about status changes. - Broad updates: Subscribe to
Case.Updated
if you want to receive all types of case updates, including status changes. - Filtering: If subscribed to
Case.Updated
, implement filtering on your side to handle specific sub events as needed.
Payload
Payload structure:
{
"event": "ENTITY_NAME.ACTION_NAME",
"sub_event": "ENTITY_NAME.SUB_ACTION_NAME " // optional
"context": { ... },
"ENTITY_NAME": { ... }
}
Context
You can find useful extra properties in the context such as:
timestamp
a UNIX timestamp of the time when the webhook is processed. You can verify that this timestamp is within 1-2 minutes of the time your system receives it to prevent replay attacks.event_id
UUID of the event. When a webhook is retried the event id is the same as the initial call.retry_count
Webhook retry counter. 0 for the initial call.remaining_retry_count
Webhook automatic remaining retry counter.
The context
also contains additional properties depending on the entity targeted by the webhook.
Event | context properties |
---|---|
Case.* | workspace |
CaseReport.* | workspace , case |
Note.* | workspace, case |
NoteComment.* | workspace, case, note |
Individual.* | workspace, case |
Company.* | workspace, case |
Check.* | workspace, case, individual, company |
DocumentOrder.* | workspace |
workspace
: contains a workspace subset with itsid
andname
.case
: contains a case subset with itsid
,external_id
,last_activity_at
,name
,tags
,contact_has_actions
,reviewer_has_actions
,flags
,metadata
andstatus
. If the case was created via a client portal, the case context will also include aclient_portal
subset, withid
,name
andtype
.note
: contains a note subset with itsid
,author.id
,author.first_name
,author.last_name
,author.email
,content
individual
: contains an individual subset withid
,first_name
,last_name
company
: contains an individual subset withid
,name
,country
The context can be useful to verify that the webhook comes from a specific workspace or build URL to the Console App.
You can see all payload definition documented in the Callbacks section (latest section of Create a webhook).
How to test webhook
When integrating a webhook, you need to provide the URL that will be called.
To quickly inspect the payload, you can use webhook.site. It will generate a unique URL and you will see the incoming HTTP Request in your browser.
To start handling webhook locally, you can use ngrok. It will generate a unique URL to expose your local machine to the internet. You can create a webhook with this URL and the incoming request will be forwarded to your machine.
> ngrok http http://localhost:4000/
# will generate an URL like https://0ebb-92-8-30-12.eu.ngrok.io
Securing webhook
We support securing webhooks through content hashing with a signature. A SHA256 HMAC signature is calculated for the content and delivered in the Dotfile-Signature
header, which can be used for comparison.
We also support secret rotation with no downtime.
If you rotate your secret, we will provide a second header
Dotfile-Old-Signature
for you to validate against your old secret. See below example for more insight.
To verify a webhook, calculate the signature from the request body using the webhook secret. It is recommended to use the raw request body content for hashing, as using JSON parsing may alter it.
Here is an example of minimal express server implementation:
import express from 'express';
import bodyParser from 'body-parser';
import { createHmac } from 'crypto';
const app = express();
const port = 3000;
// Replace with your webhook secret
const WEBHOOK_SECRET = 'dotsecret.XXXXXXXXXX';
// Add middleware to extract raw request body
app.use(
express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString();
},
})
);
// Parse the request body
app.use(bodyParser.json());
// Receive HTTP POST requests
app.post('/dotfile-webhook', (req, res) => {
const payload = req.body;
const rawBody = req.rawBody;
// Verify signature
const signature = createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
let isSignatureValid = false;
if (signature === req.headers['dotfile-signature']) {
isSignatureValid = true;
console.log('โ
Valid signature');
}
if (signature === req.headers['dotfile-old-signature']) {
isSignatureValid = true;
console.log('โ
Valid signature but has been rotated, update your config');
}
if (!isSignatureValid) {
res.sendStatus(400);
console.error('Invalid signature');
return;
}
// Do something neat with the data received!
console.log(payload);
// Finally, respond with a HTTP 200 to signal all good
res.sendStatus(200);
});
app.listen(port, () =>
console.log(`Webhook consumer listening on port ${port}!`)
);
We also include a UNIX timestamp of the time when the webhook is processed in context.timestamp
. You can verify that this timestamp is within 1-2 minutes of the time your system receives it to prevent replay attacks.
Webhook ordering
Overview
Webhooks in the Dotfile platform are delivered asynchronously, which means events are not guaranteed to arrive in the same order as they were triggered. This behavior is common in webhook-based systems due to network delays, retries and its asynchronous nature. For further insights on the challenges and debates surrounding webhook ordering, you can explore discussions here and here.
Design decision
Dotfileโs webhook system prioritizes reliability and guarantees delivery of events, but ordering is not enforced. This decision ensures scalability and robust handling of network or endpoint failures.
In scenarios where multiple events are triggered for entities such as cases, companies, individuals, checks, or document orders, enforcing strict ordering would require queuing all subsequent events until any blocking event has been successfully delivered and acknowledged without errors. Such queuing could result in the accumulation of a significant volume of events, introducing additional challenges for the integration in terms of scaling and ensuring reliability.
However, we recognize that for some use cases, maintaining the correct order of events is crucial for data synchronization.
Synchronization best practices
To mitigate issues caused by out-of-order events, we recommend the following strategies:
-
Retrieve the latest state using API calls
After receiving a webhook, you can make an additional
GET /cases/{id}
call to retrieve the latest state of the case. This ensures your data reflects the most current information. -
Track and use the case
last_activity_at
fieldEach case has a
last_activity_at
timestamp. By storing this value on your end, you can ensure synchronization only occurs for events wherelast_activity_at
is greater than or equal to the latest timestamp you have stored. This prevents updating with outdated information, even if a webhook arrives late.
Failure and retry
We expect a 2XX
response code from your endpoint when we call your webhook URL. If we receive any other response code, we will interpret it as a failure and retry the webhook with an exponential backoff until we receive a 2XX
response code.
We will retry 4 times after the initial call, respectively 1 hour, 4 hours, 13 hours and 40 hours after each failure. For instance:
- 1st automated retry: 1 hours after the initial call
- 2nd automated retry: 4 hours after the initial call (3 hours after the previous automated retry)
- 3rd automated retry: 13 hours after the initial call (9 hours after the previous automated retry)
- 4th automated retry: 40 hours after the initial call (27 hours after the previous automated retry)
If you modify your endpoint, our retry mechanism will take that into account and update the URL accordingly.
You can find retry information in the payload context or in request headers
context.event_id
or headerdotfile-event-id
- UUID of the event. When a webhook is retried the event id is the same as the initial call.context.retry_count
or headerdotfile-retry-count
- Webhook retry counter. 0 for the initial call.context.remaining_retry_count
or headerdotfile-remaining-retry-count
- Webhook automatic remaining retry counter. Max retry count: 4.
You can see and retry a webhook call manually from our Console App.
If a manual retry is successful for a given webhook call, there will be no subsequent automatic retry.
If a manual retry is unsuccessful, it will count as a retry against the automatic retry count.
Next automated retry, if any, will be done according to the interval against the initial call.
Webhook automatically set offline
If your webhook has more than 50 calls in error within a rolling window of 24h, the webook will automatically be set offline and you will receive a notification. You can update the URL and set back your webhook online via API or Console App to restart it.
You can promote a webhook to LIVE to keep it online even when there is a high amount of error. You will still received a notification for the high error rate.