Webhooks Integration Overview
Webhook integrations notify you when a verified privacy request is ready for processing. This enables you to manage the request within your infrastructure or pass it to a third-party system, allowing you to handle the request and confirm its completion via the provided callback URL.
Configure a Webhook Endpoint
The initial step in setting up a webhook integration involves creating a webhook endpoint. The endpoint will allow DataGrail to send notifications about incoming requests. Webhook notifications will be sent as HTTP POST requests with a JSON body to the configured webhook endpoint.
Depending on the request type, DataGrail will send one of the following notification types to your webhook endpoint:
- Health Check - Test the readiness of the webhook endpoint.
- Access - Send the details of an access request to be processed.
- Deletion - Send the details of a deletion request to be processed.
- Opt-Out - Send the details of an opt-out request to be processed.
Headers
DataGrail will include two custom headers in the webhook request for identification and security purposes.
Name | Description |
---|---|
X-Webhook-Signature | HMAC-SHA256 signature to verify the request originated from DataGrail. |
X-Webhook-Timestamp | Timestamp of when the request was sent. |
Example Headers
{
"X-Webhook-Timestamp": "1757462400",
"X-Webhook-Signature": "kecgzfKH7/rUIns3tA1V4xDo9g/8k+aaClx9osgcpfg=",
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": "dgclient/1.0"
}
Body
The webhook body will be sent as a JSON object, where the structure of the body will depend on the notification type.
Common Request Body Parameters
For all webhook requests, the body will contain the below common parameters.
Name | Description |
---|---|
integration_name | string The integration display name in the DataGrail application. |
integration_kind | string The integration kind, which will always be webhook . |
webhook_request_id | string(UUID) A unique identifier for the webhook request. |
request_type | enum(RequestType) The action to perform (privacy request type, or health check). |
Health Check Body
The Health Check notification body will only contain the Common Body Parameters.
{
"request_type": "health_check",
"integration_name": "Webhook Integration",
"integration_kind": "webhook",
"webhook_request_id": "3aa32ca6-e60d-4b71-981a-d1ba258fd712"
}
Access, Deletion, and Opt-Out Body Parameters
In addition to the Common Body Parameters, the Access, Deletion, and Opt-Out notification bodies will include the following specific parameters:
Name | Description |
---|---|
topic | enum(Topic) The type of request notification. |
identifiers | object(Identifiers) The data subject identifiers associated with the request. |
webhook_callback_url | string The URL to callback to once the request is complete. |
privacy_request_id | string(UUID) The UUID of the privacy request (36-character string). |
integration_id | string(UUID) The UUID of the integration (36-character string). |
{
"request_type": "access",
"integration_name": "Webhook Integration",
"integration_kind": "webhook",
"topic": "privacy_request:create",
"identifiers": {
"email": [ { "email": "john@example.com" } ],
"user_id": [ { "user_id": "100101" } ]
},
"webhook_callback_url": "https://acme.datagrail.io/api/v2/webhooks/callback",
"privacy_request_id": "4b7a02e2-361e-4957-9d69-b381e3f47758",
"integration_id": "4b7a02e2-361e-4957-9d69-b381e3f47758",
"webhook_request_id": "3aa32ca6-e60d-4b71-981a-d1ba258fd712"
}
Validation
For security purposes, we strongly recommend implementing a mechanism to validate the DataGrail webhook.
First obtain a Webhook Secret generated by DataGrail on the webhook integration connection page.
To validate the webhook request, follow these steps:
- Fetch the webhook timestamp from the request header
X-Webhook-Timestamp
(e.g.$TIMESTAMP
). - Fetch the webhook body (e.g.
$PAYLOAD
). - Concatenate the timestamp and body to a single string (e.g.
$TIMESTAMP:$PAYLOAD
). - Calculate the HMAC-SHA256 signature for the string, using the Webhook Secret.
- Convert the signature to the printable ASCII characters (base64).
- Compare the signature with the webhook signature from the request header
X-Webhook-Signature
.
Here are a few webhook validation examples for the Health Check notification:
- Shell
- JavaScript
- Python
- Ruby
#!/bin/bash
trap 'rm -f /tmp/payload.txt /tmp/original_signature.bin /tmp/computed_signature.bin' EXIT
read -p "Enter payload: " PAYLOAD
read -p "Enter timestamp: " TIMESTAMP
read -p "Enter signature: " SIGNATURE
read -sp "Enter secret key: " SECRET_KEY
printf "$TIMESTAMP:$PAYLOAD" > /tmp/payload.txt
printf "$SIGNATURE" | base64 -d > /tmp/original_signature.bin
openssl dgst -sha256 -mac HMAC -macopt key:"$SECRET_KEY" -binary /tmp/payload.txt > /tmp/computed_signature.bin
cmp -s /tmp/original_signature.bin /tmp/computed_signature.bin && printf "\nSignature is valid\n" || printf "\nSignature is not valid\n"
const crypto = require('crypto');
secret = 'dgwebhook...'
timestamp = '1758100027'
json_payload = '{"request_type":"health_check","integration_name":"Webhook Integration","integration_kind":"webhook","webhook_request_id":"9320f050-7e1e-4412-81a4-fa184184d22b"}'
const hmac = crypto.createHmac('sha256', secret);
hmac.update(`${timestamp}:${json_payload}`);
const signature = hmac.digest().toString('base64');
import hmac
import base64
import hashlib
secret = 'dgwebhook...'
timestamp = '1758100027'
json_payload = '{"request_type":"health_check","integration_name":"Webhook Integration","integration_kind":"webhook","webhook_request_id":"9320f050-7e1e-4412-81a4-fa184184d22b"}'
signature = base64.b64encode(hmac.new(secret.encode('utf-8'), f"{timestamp}:{json_payload}".encode('utf-8'), hashlib.sha256).digest()).decode('utf-8')
require 'base64'
require 'openssl'
secret = 'dgwebhook...'
timestamp = '1758100027'
json_payload = '{"request_type":"health_check","integration_name":"Webhook Integration","integration_kind":"webhook","webhook_request_id":"9320f050-7e1e-4412-81a4-fa184184d22b"}'
signature = Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', secret, "#{timestamp}:#{json_payload}"))
Expected Responses
DataGrail expects a 201 Created
HTTP response when a request has been created successfully. If the request has not been created, DataGrail expects an appropriate 4xx
or 5xx
HTTP response code. An error description is not required, but will aid in request debugging.
Error Response Body Examples
{
"error": "Invalid request signature"
}
{
"error": "Invalid request type"
}
Callbacks
Once the request has been processed, you must notify DataGrail of completion by sending a callback to the provided Callback URL.
URL
The callback URL is included in the webhook_callback_url
parameter of the webhook notification body.
API Key
The callback request must include an API key with the Webhook Callback scope. You can create an API key in DataGrail by navigating to Settings > API Keys > Create API Key.
Body
The webhook callback body will be sent as a JSON object, where the structure of the body will depend on the notification type.
Common Callback Body Parameters
For all webhook callbacks, the body will contain the below common parameters.
Name | Description |
---|---|
webhook_request_id | string A unique identifier for the webhook request. |
request_type | enum(RequestType) The notification type. |
status | enum(Status) The status of the request. |
Access Callback Body Parameters
Access callback bodies will also include the following fields:
Name | Description |
---|---|
privacy_request_id | string The UUID of the privacy request (36-character string). |
integration_id | enum(RequestType) The UUID of the integration (36-character string). |
results | object(Files or FileRefs) The status of the request. |
Max upload limit of all files is 200 MB.
{
"webhook_request_id": "3aa32ca6-e60d-4b71-981a-d1ba258fd712",
"request_type": "access",
"privacy_request_id": "4b7a02e2-361e-4957-9d69-b381e3f47758",
"integration_id": "4b7a02e2-361e-4957-9d69-b381e3f47758",
"status": "completed",
"results": {
"files": [
{ "contacts_data.txt": "cHJpdmFjeSBkYXRhIGZvciBzb21lIHVzZXI=" },
{ "events_data.txt": "cHJpdmFjeSBkYXRhIGZvciBzb21lIHVzZXIgKGV2ZW50cyk=" }
]
}
}
{
"webhook_request_id": "3aa32ca6-e60d-4b71-981a-d1ba258fd712",
"request_type": "access",
"privacy_request_id": "4b7a02e2-361e-4957-9d69-b381e3f47758",
"integration_id": "4b7a02e2-361e-4957-9d69-b381e3f47758",
"status": "completed",
"results": {
"file_refs": [
"api_integration_files_sync/{privacy_request_id_}/{integration_id}/*"
]
}
}
Deletion Callback Body Parameters
Deletion callback bodies will also include the following fields:
Name | Description |
---|---|
privacy_request_id | string The UUID of the privacy request (36-character string). |
integration_id | enum(RequestType) The UUID of the integration (36-character string). |
{
"webhook_request_id": "8cf283d3-b9d3-4f93-8195-55948c43d625",
"request_type": "deletion",
"privacy_request_id": "9b8d61c7-8432-4e7a-bc2d-7ae17232979c",
"integration_id": "716cde3d-3136-407a-bb32-dba2b4dcf1d9",
"status": "completed"
}
Opt Out Callback
Opt-out callback bodies will also include the following fields:
Name | Description |
---|---|
opt_out_request_id | string The UUID of the opt-out request (36-character string). |
integration_id | enum(RequestType) The UUID of the integration (36-character string). |
{
"webhook_request_id": "9bfacff3-c454-4212-9417-88e246e1798f",
"request_type": "opt_out",
"opt_out_request_id": "206356d2-80de-492c-bd34-b0093b6f5d25",
"integration_state_id": 786,
"status": "completed"
}
Expected Responses
See the API documentation for updating a privacy request integration.
Objects
Identifiers
An object containing key-value pairs of all identifiers for the request.
Name | Description |
---|---|
<identifier_api_name>[] | object(Identifier) The key-value pairs of the identifier category and identifier value. |
Identifier
An individual identifier object.
Name | Description |
---|---|
<identifier_category_api_name> | string The identifier value. |
Files
A list of objects containing key-value pairs of file names and file contents to upload.
Name | Description |
---|---|
files[] | object(File) An array of File objects. |
File
An individual file and its contents.
Name | Description |
---|---|
<file_name> | string The file to upload. Files must be converted to printable ASCII characters (Base64). |
FileRefs
A list of file paths in your cloud storage to sync.
Name | Description |
---|---|
file_refs[] | string An array of file paths in your cloud storage. The file paths must be located in the bucket that is integrated with DataGrail, and must use the following file path format: api_integration_files_sync/{privacy_request_id}/{integration_id}/* |
Enums
RequestType
The action to perform.
Enum | Description |
---|---|
health_check | string The request is for a health check |
access | string The request is for access. |
deletion | string The request is for deletion. |
opt_out | string The request is for opt-out |
Topic
The request notification type.
Enum | Description |
---|---|
privacy_request:create | string The request is to create a new privacy request. |
privacy_request:reminder | string The request is a reminder of an incomplete privacy request. |
opt_out_request:create | string The request is to create a new opt-out request. |
opt_out_request:reminder | string The request is a reminder of an incomplete opt-out request. |
Status
The status that is set for an integration on a privacy request.
Enum | Description |
---|---|
completed | string The integration for the request should be marked as completed. |
ignored | string The integration for the request should be marked as ignored. |
Disclaimer: The information contained in this message does not constitute as legal advice. We would advise seeking professional counsel before acting on or interpreting any material.