Integrate Webhooks

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 as name, external_id, status, flags, risk, tags, template_id, metadata, custom_properties) is updated. A sub_event is always provided in Case.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: case open โ†’ approved
    • Case.FlagsUpdated: When case flags are updated (any flag changes can trigger this event).
    • Case.ContactHasActionsUpdated: When contact_has_actions is updated.
    • Case.ReviewerHasActionsUpdated: When reviewer_has_actions is updated.
    • Case.InfoUpdated: When case info are updated (such as name, external_id or custom_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 the next_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. A sub_event is always provided in Individual.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 relevant
  • Individual.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. A sub_event is always provided in Company.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 relevant
  • Company.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 no Individual.Deleted, Company.Deleted nor Check.Deleted event.

๐Ÿ“˜

Events can have sub events for fine-grain filtering.

For instance, the top-level event Case.Updated has one sub-event Case.StatusUpdated that allow you to subscribe specifically to the update of the case status 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 other Case.*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.

Eventcontext 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 its id and name.
  • case: contains a case subset with its id, external_id, last_activity_at, name, tags, contact_has_actions, reviewer_has_actions, flags, metadata and status. If the case was created via a client portal, the case context will also include a client_portal subset, with id, name and type.
  • note : contains a note subset with its id, author.id, author.first_name, author.last_name,author.email, content
  • individual: contains an individual subset with id, first_name, last_name
  • company: contains an individual subset with id, 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:

  1. 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.

  2. Track and use the case last_activity_at field

    Each case has a last_activity_at timestamp. By storing this value on your end, you can ensure synchronization only occurs for events where last_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 header dotfile-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 header dotfile-retry-count - Webhook retry counter. 0 for the initial call.
  • context.remaining_retry_count or header dotfile-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.