API Reference
This article covers the API endpoints used between your systems and DataGrail for Data Broker Compliance. There are two dispatch patterns — implement whichever matches your infrastructure. Both patterns support deletion (DROP status code 3) and opt-out (DROP status code 4) outcomes, and share the same authentication summary and error codes documented at the end of this article.
Before implementing a dispatch pattern, ensure you have:
- Completed the Quickstart and chosen your dispatch method
- Completed Identifier Configuration and selected your DROP list types
- Completed Ingestion Format and begun delivering consumer identifiers to DataGrail
Webhook
DataGrail POSTs to a URL you configure when a DROP match occurs.
Deletion Dispatch (DataGrail → You)
POST <your-webhook-url>
Content-Type: application/json
X-Datagrail-Event: drop.deletion
X-Webhook-Timestamp: 1717027200
X-Webhook-Signature: v1=<base64(HMAC-SHA256(shared_secret, "<timestamp>:<body>"))>
Request body:
{
"schema_version": "1.0",
"event": "drop.deletion",
"ticket_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"broker_id": "4821",
"drop_session_id": "f0e1d2c3-b4a5-6789-0fed-cba987654321",
"hash_type": "email",
"match_date": "2026-05-30",
"remote_identifier": "0035f00000A1B2CAAZ",
"remote_identifier_kind": "external_id",
"callback_url": "https://yourcompany.datagrail.io/api/v2/webhooks/callback"
}
Fields:
| Field | Description |
|---|---|
event | Always "drop.deletion" — distinguishes from DSR webhooks |
ticket_uuid | DataGrail's internal tracking ID for this deletion |
broker_id | Your CalPrivacy data broker registration number |
drop_session_id | The specific DROP session/cycle this match came from |
hash_type | Which list type matched: email, phone, ndz, name_vin, maid, ctvid |
remote_identifier | Your consumer pointer — the same value you sent during ingestion |
remote_identifier_kind | How to interpret the pointer: external_id, row_uuid, email, phone |
callback_url | URL to POST your outcome to when deletion is complete |
Return 2xx to acknowledge receipt. Process the deletion asynchronously.
Signature verification: Compute HMAC-SHA256(shared_secret, "<X-Webhook-Timestamp>:<raw-body>"), Base64-encode, and compare to the value after v1= in X-Webhook-Signature.
Deletion Outcome Callback (You → DataGrail)
After processing the deletion, POST the outcome to the callback_url provided in the webhook. Use ticket_uuid from the original request to correlate the callback.
POST <callback_url>
Content-Type: application/json
Authorization: Bearer <your-datagrail-api-token>
{
"schema_version": "1.0",
"status": "completed",
"ticket_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"drop_outcome": "deleted",
"completed_at": "2026-05-30T18:45:00Z",
"failure_reason": null
}
drop_outcome values:
| Value | DROP Status Code | Meaning |
|---|---|---|
deleted | 3 | Consumer's non-exempt personal information was deleted |
not_found | 5 | Consumer not found in your system |
opted_out | 4 | Multiple consumers are linked to this identifier and all were opted out of sale or sharing (see Opt-Out Processing) |
exempt | 2 | All personal information for this consumer is exempt per Civil Code §1798.99.86 |
failed | — | Processing failed (provide failure_reason) |
ISI (Internal Systems Integration)
ISI is a customer-hosted REST API that DataGrail calls into. DROP adds two new endpoints to the existing ISI spec.
Prerequisites
Your ISI API must:
- Serve over HTTPS with TLS 1.2+
- Authenticate via OAuth 2.0 Client Credentials or static bearer token
- Advertise DROP capabilities in
/api/v1/connections/list:
{
"results": [
{
"uuid": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
"capabilities": [
"privacy/access",
"privacy/delete",
"privacy/drop/identifiers",
"privacy/drop/delete"
]
}
]
}
DROP Delete Endpoint
DataGrail calls this when a DROP match occurs and your system needs to perform the deletion.
POST /api/v1/privacy/drop/delete/<connection-uuid>
Authorization: Bearer <token>
Content-Type: application/json
Request body:
{
"request_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"results_token": "a1b2c3d4e5f67890",
"callback_path": "/api/v2/internal_systems/request_callback",
"drop_context": {
"broker_id": "4821",
"drop_session_id": "f0e1d2c3-b4a5-6789-0fed-cba987654321",
"hash_type": "email",
"match_date": "2026-05-30"
},
"remote_identifier": "0035f00000A1B2CAAZ",
"remote_identifier_kind": "external_id"
}
Your immediate response:
HTTP/1.1 200 OK
{"status": "processing"}
Your callback when deletion completes:
{
"status": "completed",
"results_token": "a1b2c3d4e5f67890",
"drop_outcome": "deleted",
"completed_at": "2026-05-30T18:45:00Z"
}
DROP deletions have different requirements than standard DSR deletions — different regulatory timelines, audit trails, and customer-side workflows. A separate endpoint ensures you can implement DROP-specific handling without affecting your DSR pipeline.
DROP Identifier Retrieval Endpoint
DataGrail calls this to retrieve clear identifiers for a matched consumer so it can issue deletions to 3rd-party SaaS systems (Salesforce, HubSpot, etc.) on your behalf.
POST /api/v2/privacy/drop/identifiers/<connection-uuid>
Authorization: Bearer <token>
Content-Type: application/json
Request body:
{
"request_uuid": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"remote_identifier": "0035f00000A1B2CAAZ",
"remote_identifier_kind": "external_id",
"identifier_categories": ["email", "phone"]
}
Your synchronous response:
HTTP/1.1 200 OK
{
"identifiers": {
"email": [{"email": "alice@example.com"}, {"email": "alice.work@example.com"}],
"phone": [{"phone": "+15551234567"}]
}
}
Only after a DROP match, and only if you have 3rd-party SaaS connections configured in DataGrail that require clear identifiers to process deletions. If you only need internal deletions handled by the DELETE endpoint above, this endpoint is not required.
Opt-Out Processing
An opt-out occurs when multiple consumers are linked to the same identifier and all are opted out of the sale or sharing of their personal information — rather than having their data deleted. This is DROP status code 4, as defined in CCR Title 11, §7614(b)(2)(D).
When to Return opted_out
Return drop_outcome: "opted_out" in your callback when:
- The matched identifier resolves to multiple consumers in your system
- Your system opts all of those consumers out of sale or sharing rather than deleting their data
- The consumer's data is retained but no longer sold to or shared with third parties
Opt-out does not remove data — it suppresses sale and sharing. Use opted_out when you retain the consumer's data under an opt-out rather than performing a full deletion. If you delete the data entirely, use deleted (code 3) instead.
Opt-Out Callback Format
The callback fields are the same across both dispatch patterns — the only difference is the correlation field used to match the response to the original request. Return drop_outcome: "opted_out" with the resolved consumer email:
{
"schema_version": "1.0",
"status": "completed",
"ticket_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"results_token": "a1b2c3d4e5f67890",
"drop_outcome": "opted_out",
"identifiers": {
"email": [{"email": "consumer@example.com"}]
},
"completed_at": "2026-05-30T18:45:00Z"
}
| Field | Required | Description |
|---|---|---|
status | Yes | Must be "completed" |
ticket_uuid | Webhook only | Correlates the response to the original webhook dispatch |
results_token | ISI only | Correlates the response to the original ISI request |
drop_outcome | Yes | "opted_out" for opt-out processing |
identifiers.email[0].email | Yes | The resolved consumer email address |
completed_at | Yes | ISO 8601 timestamp of when the opt-out was applied |
Use ticket_uuid for webhook callbacks and results_token for ISI callbacks. Include whichever field was provided in the original dispatch request.
Return the consumer email address exactly as stored in your system. Do not trim whitespace, lowercase, or otherwise normalize the email value. DataGrail uses the exact value you return to create the opt-out record.
Opt-Out Processing Lifecycle
The following diagram illustrates the end-to-end flow when your system responds with an opt-out outcome:
- DataGrail downloads the deletion list from the DROP registry and matches hashed identifiers against your stored consumer records.
- A match is found and DataGrail dispatches the request to your system using your configured dispatch method.
- Your system resolves the identifier and determines that multiple consumers are linked to it.
- Your system applies an opt-out (suppresses sale/sharing) for all linked consumers.
- Your system POSTs the callback to DataGrail with
drop_outcome: "opted_out"and the resolved consumer email. - DataGrail creates an opt-out record, updates the request status, and reports code 4 to the DROP registry.
Opt-Out Outcome by Pattern
Both patterns use the same callback fields — the only difference is how the callback reaches DataGrail:
| Pattern | How to respond with opt-out |
|---|---|
| Webhook | POST to the callback_url from the original webhook with drop_outcome: "opted_out" and ticket_uuid |
| ISI | POST to callback_path with drop_outcome: "opted_out" and results_token |
Authentication Summary
The following table summarizes the authentication method for each dispatch pattern:
| Pattern | Auth Method |
|---|---|
| Webhook (DataGrail → you) | HMAC-SHA256 signature on each request |
| Webhook callback (you → DataGrail) | Bearer token (provided by DataGrail) |
| ISI (DataGrail → you) | OAuth 2.0 Client Credentials or static bearer token |
Error Codes
The following HTTP status codes may be returned by DataGrail endpoints:
| Code | Meaning |
|---|---|
200 | Success |
202 | Accepted (async processing started) |
400 | Bad request — check response body for validation details |
401 | Authentication failed |
403 | Insufficient permissions |
404 | Resource not found |
409 | Conflict (e.g., duplicate upload in progress) |
429 | Rate limited — respect Retry-After header |
500 | Internal server error |
Error response body:
{
"error": "validation_failed",
"message": "schema_version '2.0' is not supported",
"supported_versions": ["1.0"]
}
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.