Webhooks

Developer guide on how to set up Labelbox webhooks through Python SDK

See Webhooks guide page for details on how to use webhooks to receive notifications from activities in Labelbox.

Example configurations to receive requests

from flask import Flask, request import json import hmac import hashlib # This can be any secret that matches your webhook config (we will set later) secret = b"<replace with secret>" # Example for server-side code to receive webhook events app = Flask(__name__) @app.route("/webhook-endpoint", methods=["POST"]) def print_webhook_info(): payload = request.data computed_signature = hmac.new(secret, msg=payload, digestmod=hashlib.sha1).hexdigest() if request.headers["X-Hub-Signature"] != "sha1=" + computed_signature: print( "Error: computed_signature does not match signature provided in the headers" ) return "Error", 500, 200 print("=========== New Webhook Delivery ============") print("Delivery ID: %s" % request.headers["X-Labelbox-Id"]) print("Event: %s" % request.headers["X-Labelbox-Event"]) print("Payload: %s" % json.dumps(json.loads(payload.decode("utf8")), indent=4)) return "Success" thread = threading.Thread(target=lambda: run_simple("0.0.0.0", 3001, app)) thread.start()
import json import hmac import hashlib import re secret = "<replace with secret>" # Example of a AWS Lambda to receive webhook events def lambda_handler(event, context): webhook = re.sub(r'^sha256=', '', event['headers']['x-hub-signature-256']) digest = hmac.new( key = bytes(secret, 'utf-8'), msg = event['body'].encode('utf-8'), digestmod = hashlib.sha256 ) if webhook != digest.hexdigest(): return { 'statusCode': 400, 'body': 'Error: computed_signature does not match' } print("=========== New Webhook Delivery ============") print("Delivery ID: %s" % event["headers"]["x-labelbox-id"]) print("Event: %s" % event["headers"]["x-labelbox-event"]) print("=========== Payload =============") print(event["body"]) return "Success"
import functions_framework import json import hmac import hashlib secret = b"<replace with secret>" # Example of a Google Cloud Function to receive webhook events @functions_framework.http def hello_http(request): payload = request.data computed_signature = hmac.new(secret, msg=payload, digestmod=hashlib.sha1).hexdigest() if request.headers["X-Hub-Signature"] != "sha1=" + computed_signature: print( "Error: computed_signature does not match signature provided in the headers" ) return "Error", 500, 200 print("=========== New Webhook Delivery ============") print("Delivery ID: %s" % request.headers["X-Labelbox-Id"]) print("Event: %s" % request.headers["X-Labelbox-Event"]) print("Payload: %s" % json.dumps(json.loads(payload.decode("utf8")), indent=4)) return "Success"
import azure.functions as func import json import hmac import logging import hashlib secret = b"<replace with secret>" # Example of a Azure Function to recieve webhook events app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION) @app.route(route="http_trigger") def http_trigger(req: func.HttpRequest) -> func.HttpResponse: payload = req.get_body() computed_signature = hmac.new(secret, msg=payload, digestmod=hashlib.sha1).hexdigest() if req.headers["X-Hub-Signature"] != "sha1=" + computed_signature: logging.warning( "Error: computed_signature does not match signature provided in the headers" ) return "Error", 500, 200 logging.info("=========== New Webhook Delivery ============") logging.info("Delivery ID: %s" % req.headers["X-Labelbox-Id"]) logging.info("Event: %s" % req.headers["X-Labelbox-Event"]) logging.info("Payload: %s" % json.dumps(json.loads(payload.decode("utf8")), indent=4)) return "Success"

Create webhook

Webhook v2 setup

You can create a v2 webhook through the Labelbox UI. The v2 webhook mimics the Export v2 payload.

Webhook v1 setup

As shown below, you can create a v1 webhook with the Python SDK or the Labelbox UI, which is similar to v2 webhooks.

from labelbox import Client, Webhook public_url = "https://example.com/webhook-endpoint" # Your server's public url - where the messages will be sent to secret = b"CHANGE-ME" # Use to verify Labelbox is sending the message client = Client(api_key="<YOUR_API_KEY>") project = client.get_project("<project_id>") print([topic.value for topic in Webhook.Topic]) # See all available topics secret = b"example_secret" # This can be any secret that matches your webhook config (we will set later) webhook = Webhook.create(client, topics=["LABEL_CREATED"], url="public_url", secret=secret.decode(), project=project)

Get webhooks

# Fetch all webhooks org = client.get_organization() webhooks = org.webhooks() #paginated # Fetch project webhooks project = client.get_project("<project_id>") webhooks = project.webhooks() #paginated webhook = next(webhooks) status = webhook.status server_url = webhook.url topics = webhook.topics project = webhook.project()

Update webhook

# url, topics, and status can all be updated updated_url = f"{public_url}/webhook-endpoint" webhook.update(url=updated_url, topics=[Topic.LABEL_DELETED], status=Webhook.Status.INACTIVE.value)

Delete webhook

webhook.delete()

Sample response

v1

{'agreement': None, 'benchmarkAgreement': None, 'createdAt': '2023-02-13T18:22:46Z', 'dataRow': {'createdAt': '2023-01-25T17:54:20Z', 'deletedAt': None, 'externalId': 'multi_thread_update_4cfdcb29-be33-4e07-b586-849829385210', 'id': 'cldbyu7c7n4jp072scuqt66ll', 'labelCountInProject': 1, 'rowData': 'https://labelbox.s3-us-west-2.amazonaws.com/datasets/mapillary_traffic/images/F1xqHzDSi1qP3qSLBXVyJQ.jpg', 'updatedAt': '2023-01-25T20:42:57Z'}, 'dataset': {'createdAt': '2023-01-25T17:53:58Z', 'deleted': False, 'description': '', 'id': 'cldbytq0w2uw8074hgl3hbxr2', 'name': 'Datarow Update Test - 5K', 'updatedAt': '2023-01-25T17:54:21Z'}, 'deleted': False, 'id': 'cle357uex0g5307z996y28ic0', 'label': '{"objects":[{"featureId":"cle357vrp00013b6jsi6dfei0","schemaId":"cldan3u0701sc07yi6xyw6up6","color":"#ff0000","title":"bounding_box","value":"bounding_box","bbox":{"top":453,"left":951,"height":670,"width":952},"instanceURI":"https://api.labelbox.com/masks/feature/cle357vrp00013b6jsi6dfei0"}],"classifications":[],"relationships":[]}', 'project': {'createdAt': '2023-02-13T18:20:52Z', 'deleted': False, 'description': '', 'id': 'cle355i370h86070hf1v7ata1', 'name': 'Webhooks', 'updatedAt': '2023-02-13T18:22:17Z'}, 'secondsToLabel': 0, 'skipped': False, 'updatedAt': '2023-02-13T18:22:46Z', 'user': {'email': 'lmoehlenbrock@labelbox.com', 'id': 'clbpgxfaehww6076tedo5ejwx'}}
{ "id": "cl4g555g81ltr07cka1a2b1wv", "createdAt": "2022-06-16T19:41:49Z", "updatedAt": "2022-06-16T19:58:25Z", "secondsToLabel": 6.002, "label": "{\"objects\":[{\"featureId\":\"cl4hg2phm00013b6j2thsko0u\",\"schemaId\":\"cl2i6hbju1o2y10a321w3cusv\",\"title\":\"grape\",\"value\":\"grape\",\"color\":\"#1CE6FF\",\"bbox\":{\"top\":298,\"left\":445,\"height\":520,\"width\":930},\"instanceURI\":\"https://api.labelbox.com/masks/feature/cl4hg2phm00013b6j2thsko0u\"}],\"classifications\":[],\"relationships\":[]}", "agreement": null, "benchmarkAgreement": null, "deleted": false, "skipped": false, "labelCountInProject": 2, "project": { "id": "cl2i6gssn1o2210a3gq549fq0", "createdAt": "2022-04-27T22:57:43Z", "updatedAt": "2022-06-15T22:19:36Z", "name": "grapes", "description": "", "deleted": false }, "dataRow": { "id": "cl2hzfg8g18kw0ztwbtnq0b6q", "createdAt": "2022-04-27T19:40:43Z", "updatedAt": "2022-04-27T20:13:51Z", "deletedAt": null, "externalId": "CFR_1620.jpg", "rowData": "https://storage.labelbox.com/ckhmnux5zeutm0825bt0kj34y%2F5375dc13-6442-22d7-f523-e9d252e715ea-CFR_1620.jpg?Expires=1656619105&KeyName=labelbox-assets-key-3&Signature=iOg3tbKe4j7xWZHw1r5rQD4iJIk=" }, "dataset": { "id": "cl2hz9qay0unl10a3f4wmcsfi", "createdAt": "2022-04-27T19:36:16Z", "updatedAt": "2022-04-27T19:40:43Z", "name": "data", "description": "", "deleted": false }, "user": { "id": "ckhmnux6hi0p907898jext199", "email": "rahul@labelbox.com" } }
{ "id": "cl2i6hegh1x660z9i9fzz01a0", "createdAt": "2022-04-27T22:58:35Z", "updatedAt": "2022-06-16T20:12:41Z", "secondsToLabel": 31.448, "label": "{\"objects\":[{\"featureId\":\"cl2i6hhgd00013f6ghfnrcn6h\",\"schemaId\":\"cl2i6hbju1o2y10a321w3cusv\",\"title\":\"grape\",\"value\":\"grape\",\"color\":\"#1CE6FF\",\"bbox\":{\"top\":701,\"left\":402,\"height\":316,\"width\":238},\"instanceURI\":\"https://api.labelbox.com/masks/feature/cl2i6hhgd00013f6ghfnrcn6h\"},{\"featureId\":\"cl2i6hp3h00033f6gaofl29lx\",\"schemaId\":\"cl2i6hbju1o3010a30ksn2yiy\",\"title\":\"grape mask\",\"value\":\"grape_mask\",\"color\":\"#FF34FF\",\"instanceURI\":\"https://api.labelbox.com/masks/feature/cl2i6hp3h00033f6gaofl29lx\"}],\"classifications\":[],\"relationships\":[]}", "agreement": null, "benchmarkAgreement": null, "deleted": true, "skipped": false, "labelCountInProject": 1, "project": { "id": "cl2i6gssn1o2210a3gq549fq0", "createdAt": "2022-04-27T22:57:43Z", "updatedAt": "2022-06-16T20:06:25Z", "name": "grapes", "description": "", "deleted": false }, "dataRow": { "id": "cl2hzfg8g18kk0ztwg22ke4vz", "createdAt": "2022-04-27T19:40:43Z", "updatedAt": "2022-04-27T20:13:52Z", "deletedAt": null, "externalId": "SYH_2017-04-27_1324.jpg", "rowData": "https://storage.labelbox.com/ckhmnux5zeutm0825bt0kj34y%2F1d883928-046f-97d5-e3f7-e6c296967c7e-SYH_2017-04-27_1324.jpg?Expires=1656619961&KeyName=labelbox-assets-key-3&Signature=0ma5Yw5qxdJE6_x7ACR7aPEHePM=" }, "dataset": { "id": "cl2hz9qay0unl10a3f4wmcsfi", "createdAt": "2022-04-27T19:36:16Z", "updatedAt": "2022-04-27T19:40:43Z", "name": "data", "description": "", "deleted": false }, "user": { "id": "ckhmnux6hi0p907898jext199", "email": "rahul@labelbox.com" } }
{ "organizationId": "cl81imdtk0cie0yx4g39i0du8", "projectId": "clbxofp0h05d2074475g14f9v", "action": "APPROVE", "dataRowIds": [ "cl9elx44f001i076k2pz84tw6" ], "originTaskId": "92665e1f-a507-4308-bdfc-d6be3c629ec7", "originTaskName": "My custom review step", "destinationTaskId": null, "destinationTaskName": "Done", "actorId": "cl81imdtx0cif0yx4ak0ndq8a", "timestamp": "2023-01-20T12:06:01.421Z" }

v2

{ "data_row": { "id": "clvmpaetz76ct0706730rfcq9", "external_id": null, "global_key": "TEST-ID-157128604429964085398241585654637789196d", "row_data": "https://storage.googleapis.com/labelbox-datasets/People_Clothing_Segmentation/jpeg_images/IMAGES/img_0001.jpeg", "metadata_fields": [], "details": { "dataset_id": "clvmpa9et010j0750rlgs8qdi", "created_at": "2024-04-30T18:07:10.82Z", "updated_at": "2024-04-30T18:07:14.199Z", "created_by": null } }, "projects": { "clvo7ajpc014907zxagiw9hry": { "project_name": "test_lambda", "labels": [ { "label_kind": "Default", "id": "clvo7b6h501ic07fdbg62c32w", "label_details": { "created_at": "2024-05-06T17:18:48Z", "updated_at": "2024-05-06T17:18:48Z", "created_by": "gunderwood@labelbox.com" }, "performance_details": { "seconds_to_create": 5759.536, "seconds_to_review": 0, "skipped": false, "benchmark_reference_label": null, "benchmark_score": null, "consensus_score": null }, "annotations": { "classifications": [], "objects": [ { "bbox": { "height": 359, "left": 24, "top": 85, "width": 480 }, "color": "#1976d2", "feature_id": "clvv876mw00023b6qr6mk2o4s", "instance_uri": "https://api.labelbox.com/masks/feature/clvv876mw00023b6qr6mk2o4s", "schema_id": "clvcnmppk0aq507z11haa4x66", "title": "person", "value": "person" } ], "relationships": [] } } ], "project_details": { "ontology_id": "clvcnmpor0aq207z14jesh8w7", "batch_id": "11EF07D4F9E60FC08820734F207E70B1", "priority": 5, "consensus_expected_label_count": { "Int32": 1, "Valid": true } } } }, "media_attributes": { "asset_type": "image", "content_length": 122898, "exif_rotation": 1, "height": 825, "mime_type": "image/jpeg", "sub_type": "jpeg", "super_type": "image", "width": 550 } }
{ "data_row": { "id": "clvmpaetz76ct0706730rfcq9", "external_id": null, "global_key": "TEST-ID-157128604429964085398241585654637789196d", "row_data": "https://storage.googleapis.com/labelbox-datasets/People_Clothing_Segmentation/jpeg_images/IMAGES/img_0001.jpeg", "metadata_fields": [], "details": { "dataset_id": "clvmpa9et010j0750rlgs8qdi", "created_at": "2024-04-30T18:07:10.82Z", "updated_at": "2024-04-30T18:07:14.199Z", "created_by": null } }, "projects": { "clvo7ajpc014907zxagiw9hry": { "project_name": "test_lambda", "labels": [ { "label_kind": "Default", "id": "clvo7b6h501ic07fdbg62c32w", "label_details": { "created_at": "2024-05-06T17:18:48Z", "updated_at": "2024-05-06T17:18:48Z", "created_by": "gunderwood@labelbox.com" }, "performance_details": { "seconds_to_create": 5759.536, "seconds_to_review": 0, "skipped": false, "benchmark_reference_label": null, "benchmark_score": null, "consensus_score": null }, "annotations": { "classifications": [], "objects": [ { "bbox": { "height": 359, "left": 24, "top": 85, "width": 480 }, "color": "#1976d2", "feature_id": "clvv876mw00023b6qr6mk2o4s", "instance_uri": "https://api.labelbox.com/masks/feature/clvv876mw00023b6qr6mk2o4s", "schema_id": "clvcnmppk0aq507z11haa4x66", "title": "person", "value": "person" } ], "relationships": [] } } ], "project_details": { "ontology_id": "clvcnmpor0aq207z14jesh8w7", "batch_id": "11EF07D4F9E60FC08820734F207E70B1", "priority": 5, "consensus_expected_label_count": { "Int32": 1, "Valid": true } } } }, "media_attributes": { "asset_type": "image", "content_length": 122898, "exif_rotation": 1, "height": 825, "mime_type": "image/jpeg", "sub_type": "jpeg", "super_type": "image", "width": 550 } }
{ "data_row": { "id": "clvmpaetz76cp0706m3jw8ww4", "external_id": null, "global_key": "TEST-ID-157128695542350976802229818230180675596d", "row_data": "https://storage.googleapis.com/labelbox-datasets/People_Clothing_Segmentation/jpeg_images/IMAGES/img_0004.jpeg", "metadata_fields": [], "details": { "dataset_id": "clvmpa9et010j0750rlgs8qdi", "created_at": "2024-04-30T18:07:10.819Z", "updated_at": "2024-04-30T18:07:14.196Z", "created_by": null } }, "projects": { "clvo7ajpc014907zxagiw9hry": { "project_name": "test_lambda", "labels": [ { "label_kind": "Default", "id": "clvo7b02m01p807znd5bwfr2v", "label_details": { "created_at": "2024-05-01T19:19:31Z", "updated_at": "2024-05-06T17:22:06Z", "created_by": "gunderwood@labelbox.com" }, "performance_details": { "seconds_to_create": 3.75, "seconds_to_review": 0, "skipped": false, "benchmark_reference_label": null, "benchmark_score": null, "consensus_score": null }, "annotations": { "classifications": [], "objects": [ { "bbox": { "height": 540, "left": 130, "top": 82, "width": 311 }, "color": "#1976d2", "feature_id": "clvo7b6qr00023b6q4xe4nnff", "instance_uri": "https://api.labelbox.com/masks/feature/clvo7b6qr00023b6q4xe4nnff", "schema_id": "clvcnmppk0aq507z11haa4x66", "title": "person", "value": "person" } ], "relationships": [] } } ], "project_details": { "ontology_id": "clvcnmpor0aq207z14jesh8w7", "batch_id": "11EF07D4F9E60FC08820734F207E70B1", "priority": 5, "consensus_expected_label_count": { "Int32": 1, "Valid": true } } } }, "media_attributes": { "asset_type": "image", "content_length": 167155, "exif_rotation": 1, "height": 825, "mime_type": "image/jpeg", "sub_type": "jpeg", "super_type": "image", "width": 550 } }
{ "organizationId": "cloordigw0091073kc6pohyab", "projectId": "clvo7ajpc014907zxagiw9hry", "action": "MOVE", "dataRowIds": [ "clvmpaetz76ct0706730rfcq9" ], "originTaskId": "b363420e-1ea9-09dd-914c-d105efc5785a", "originTaskName": "Initial labeling task", "destinationTaskId": "c7a8a45d-c37d-4dba-bde7-792581597b10", "destinationTaskName": "Initial review task", "actorId": "cloordihe0092073kcorncj7g", "timestamp": "2024-05-06T17:18:48.797Z" }