The US Privacy signal has been deprecated as of January 31, 2024. We strongly advise all users of the US Privacy String to transition to the Global Privacy Platform.
This specification defines the Data Deletion Request (DDR), which is the mechanism by which the IAB CCPA Compliance Framework complies with Section 1798.105(c) of the California Consumer Protection Act (CCPA) which states "[a] business that receives a verifiable consumer request from a consumer to delete the consumer's personal information [shall] . . direct any service providers to delete the consumer's personal information from their records." The DDR is a technical contract between a Publisher and Vendors in order to enable a consumer on a Publisher's digital property to direct Vendors to delete the consumer's personal information from their records. As stated, the DDR exists primarily to comply with CCPA but may be used for deletion requests outside the domain of CCPA governance.
The technical solution detailed in this specification provides the means to signal consumer requests for data deletion. Companies supporting the US Privacy Framework (i.e., service providers) will respond to the signals by deleting the consumer's relevant personal data to the extent required by CCPA. The process for deletion depends on the company's technology and operational practices in place. How a Vendor deletes a consumer's personal data is out of scope for this specification.
Limited Service Provider Agreement (12-2-19 version)
Every Vendor that provides a service for a Publisher must host a JavaScript file for that Publisher. The Publisher must include the Vendor-provided JavaScript file as a script html element with a src attribute equal to the Vendor's specified hosting URL on every page that the Publisher intends to invoke the Data Deletion Request (eg. <script src="https://www.vendor-123.com/privacy/ddr.js"></script>). The Vendor-hosted JavaScript registers a Vendor-proprietary callback function with the USP API to be invoked if a deletion request occurs.
A Data Deletion Request is accomplished by the registerDeletion and performDeletion Commands invoked on the USP API.
registerDeletionis executed by the Vendor-hosted script upon load (immediately), which registers the Vendor-proprietary callback to be invoked when theperformDeletionis invoked.performDeletionis staged to execute by a Publisher upon a consumer taking action to request personal data be deleted. The USP API will call all Vendor callbacks registered with theregisterDeletionCommand.
This Command registers a Vendor-specific callback function with the USP API. The callback will only be called when the performDeletion Command is invoked.
| Argument Name | Type | Value |
|---|---|---|
| command | string | 'registerDeletion' |
| version | number | US Privacy spec version |
| callback | function | function() |
Example
__uspapi("registerDeletion", version, (identifiers) => {
// do proprietary delete stuff
})The Publisher, or its CMP where applicable, invokes this Command when a consumer action to initiate the deletion process occurs. The Command invokes all callbacks registered via the registerDeletion Command in no specified order.
| Argument Name | Type | Optional | Value |
|---|---|---|---|
| command | string | 'registerDeletion' |
|
| version | number | US Privacy spec version | |
| callback | null | no callback | |
| param | Identifiers |
X | Optional Identifiers object for In-App |
Example
__uspapi("performDeletion", version, null, {
"platform": "ios",
"app_identifier": "01234567891446075923",
"user_identifier": "AEA12347583AACD-A123667-A418AABC-AB123806-1242AEAACB12AB1234548606"
})The callback parameter of the __uspapi is not used in this case and shall be passed as null. The Identifiers argument is only required when handling in-app deletion requests.
When operating in an in-app environment that leverages WebViews (Mobile, CTV, etc), cookies do not persist beyond a session. Without persistant cookies, Vendors will need more information to correctly identify the consumer and the assosciated data to delete. When WebView limitations exist, A Publisher shall invoke the performDeletion with an Identifiers object as an argument for the Param. This Identifiers object will contain the platform name, the unique app identifier used in the app store, and the device identifier for that platform / store. A Publisher shall open a WebView with a web page where a consumer can complete the request to have their data deleted.
The performDeletion Command may be invoked with an optional Identifiers arguments as the Param. A hosting app passes app-specific identifier information to the WebView that invokes the performDeletion Command so that it may construct an Identifiers object to apply as an agrument.
/**
* "platform": string // see table
* "app_id": string // platform specific app identifier
* "user_id": string // IDFA on apple, or AAID on android platforms
*/
{
"platform": "ios",
"app_identifier": "01234567891446075923",
"user_identifier": "AEA12347583AACD-A123667-A418AABC-AB123806-1242AEAACB12AB1234548606"
}The following is the list of platform identifiers.
| Platform Name | Store | Identifier |
|---|---|---|
| Android | Google Play Store | "google" |
| Android | Amazon Store | "amazon" |
| iOS | App Store | "ios" |
| Samsung | App Store | "samsung" |
| Huawei | App Store | "huawei" |
| Sony | App Store | "sony" |
| LG | App Store | "lg" |
In this example, the Publisher has an Array of strings. Each of those strings contains a URL to a Vendor delete script src. This Publisher also has a CCPA delete button with the class name ccpa-delete on their page. First the script will append all of the Vendor scripts to the body of the document and then stage a listener to listen for a user clicking the ccpa-delete button. When the user clicks that button, the handler function will call the 'performDeletion' Command on the __uspapi function.
// Add all Vendor scripts; this is just an array of string sources
vendorDeleteScriptSources.forEach((vendorDeleteScriptSource) => {
const scriptElement = document.createElement("script");
scriptElement.src = vendorDeleteScriptSource;
document.body.appendChild(scriptElement);
});
function onCCPADelete() {
__uspapi('performDeletion', 1);
}
const ccpaDeleteButton = document.getElementsByClassName('ccpa-delete')[0];
ccpaDeleteButton.addEventListener('click', onCCPADelete);Below is an example script demonstrating how a vendor script can properly handle receiving the data deletion directive request from the consumer.
((win) => {
/**
* The details of this function will be proprietary to each Vendor.
*/
const deletePersonalData = () => {
// ... do some proprietary deletion work …
};
const command = 'registerDeletion';
const version = 1;
/**
* If this script is executing at the top level then the
* usp api can be called directly instead of using a
* postMessage call.
*/
if (win === win.top) {
/**
* note: you may want some error handling here in case,
* for whatever reason, the __usapi doesn't exist.
*/
win.__uspapi(command, version, deletePersonalData);
} else {
/**
* Because this script is operating in an iframe,
* postMessage will need to be used to register the
* deletePersonalData function.
*/
let uspapiFrame;
let frame = win;
/**
* Starting with the current Window, search up each
* parent frame until the frame with the name
* '__uspapiLocator' is found or there are no more
* parent frames.
*/
while (frame) {
try {
// this throws an error if it's undefined
if (frame.frames['__uspapiLocator']) {
// found
uspapiFrame = frame;
break;
}
} catch (ignore) {}
if (frame === win.top) {
// there are no more parents
break;
}
// go up
frame = frame.parent;
}
if (uspapiFrame) {
/**
* The frame containing the __uspapi api has been
* discovered, but because of this script is
* executing within and iframe, it can not access
* the method directly and so it needs to use the
* postMessage method defined in the USP API spec.
*
* First a listener needs to be set up to listen
* for the callback, then the postMessage call
* will need to be executed on the frame that
* contains the __uspapi function.
*/
win.addEventListener('message', () => {
let json = {};
try {
/**
* Because this is somewhat of an "open
* channel" all messages will be received
* not all will be JSON and so this part is
* wrapped in a try/catch and will ignore
* any errors in parsing
*/
json = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
} catch (ignore) {}
if (json.__tcfapiReturn) {
// should be executed at window scope
deletePersonalData();
} // else this is some message we don't recognize
}, false);
uspapiFrame.postMessage({
__uspapiCall: {
command: command,
version: version,
callId: 1,
},
}, '*');
} else {
// Error, no way to call the uspapi from this iframe...
}
}
})(window);