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 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 deleted
  • Company lifecycle
    • Company created
    • Company updated
    • 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

πŸ“˜

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.

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

πŸ“˜

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.

Payload

Payload structure:

{
  "event": "ENTITY_NAME.ACTION_NAME",
  "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 context contains a workspace subset with its id and name.
  • case: contains a case subset with its id, external_id, name, tags, contact_has_actions, reviewer_has_actions, flags, metadata and status.
  • note : contains a note subset with its id, author.id, author.first_name, author.last_name, author.email, content
  • individual context contains an individual subset with id, first_name, last_name
  • company context 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.

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 failure within a rolling window of 24h, it will automatically set your webhook offline. You can update the URL and set back your webhook online via API or Console App to restart it.