Webhooks allow your application to receive updates in realtime when certain events occur on Darwin. This is usually faster than polling for updates.

We are currently building out support for more event types, if you are not seeing an event you need in the list below, please reach out to us and we might be able to prioritize it.

Event typeDescriptionPayload
workflow_completeTriggered when an item in a dataset changed into the complete status.dataset_id

Life cycle

When an event is generated on Darwin, the system looks for any webhooks that are associated with the current dataset and event type. This webhook (which is an URL) will then receive a POST request containing the event_type + payload. Darwin expects an empty 2XX reply, any other return code will be considered an error and might lead to a retry of the event a few minutes later.

    "event_type": "workflow_complete",
    "payload": {
        "dataset_id": 1,
        "filename": "letter.png",
        "annotations": [
                "class_name": "FROM",
                "data": {
                    "bounding_box": {
                       "x": 455,
                       "y": 300,
                       "w": 100,
                       "h": 20
                    "text": {
                        "text": "Mail office"

See https://docs.v7labs.com/reference/darwin-json for details on the annotation format.


Multiple webhooks

There might be several webhooks applicable for an event, in that case each webhook will be fired.

Validate webhook signature

The header v7-signature provides a hmac with SHA-256 hash based on the webhook payload and time it was sent. The key to verify the hash is available to team owners and admins in the webhook_signing_key field of the GET /api/teams/<:id> API.

The header has the structure t=1623224691,v1=AA6F647F5ABB6D383E56697D71F53...

t is the UTC time in seconds that the webhook was sent. To avoid replay attacks; it is suggested to only process webhooks that are no older than a certain fixed period.
v1: is currently the only supported schema. Future updates may introduce additional versioning.

After extracting the values for t and v1; create the payload to be hashed by concatenating t + . + request body. The below python script demonstrates how to recreate the hmac; with the concatenation on the line: t + "." + request_body. The request body is the JSON payload which V7 sends.

The value provided from the v1 key can be compared to the locally generated hash to ensure the payload originated from V7. A constant time compare algorithm should be used when comparing the values.

# For basic testing update this script with your obtained key
# Change port from 9090 if desired
# Start the server with python3 validate_webhook_hash.py
# Register a webhook on a dataset pointing out to running script; and push an image through to complete.

from http.server import HTTPServer, BaseHTTPRequestHandler
import hmac
import hashlib

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
	def do_POST(self):
		print("got post!!")
		content_len = int(self.headers['content-length'])
		v7_signature = self.headers['v7-signature']
		request_body = self.rfile.read(content_len).decode("utf-8")
		print("v7-signatures: " + v7_signature)
		print("request_body: " + request_body)

		# Example signature payload
		# v7-signature:    t=1623224691,v1=AA6F647F5ABB6D383E56697D71F530EDBA7A0415C7E30986FA5ADD297A87AF77
		# v7-signature <- name of the header
		# then the string value is the t=...,v1=... which contain the timestamp and the hash respectively

		verify_fields = v7_signature.split(sep=",")
		t = ""
		keyed_hash = ""
		for pair in verify_fields:
			key,value = pair.split("=")
			if (key == "t"):
				t = value
			if (key == "v1"):
				keyed_hash = value

		print ("t: " + t)
		print ("keyed_hash: " + keyed_hash)

		mes = t + "." + request_body
		key = "9748a75c-67c9-46b5-9247-20cb109cf86d"
		keyed_hash_of_payload = hmac.new( key.encode(), mes.encode(), hashlib.sha256).hexdigest().upper()
		print ("keyed_hash_test: " + keyed_hash_of_payload)
		print ("match: " + str(hmac.compare_digest(keyed_hash.upper(), keyed_hash_of_payload)))

httpd = HTTPServer(('localhost', 9090), SimpleHTTPRequestHandler)

Next up