Implement a CCPA 'Right of Access' Workflow in CloudWright

January 26, 2020

On January 1, 2020, CCPA came into effect, introducing new privacy-centric reporting requirements for any company which holds data about California residents — and many data companies are scrambling to come into compliance.

One right that CCPA grants California residents is the 'Right of Access'. Upon request, a data business must tell an individual what data they hold about the resident. For a company subject to 'Right of Request' requirements, the workflow to generate this report will look something like this:


  • The CA resident submits their name, email, and photo ID
  • A compliance officer approves or rejects the request
  • The officer aggregates user information in internal databases
  • The officer emails a summary report to the requester

Implementing this 'Right of Access' workflow is challenging for a few reasons:

  • Companies must accurately verify the identity of the requesting user
  • The compliance application must aggregate data across many internal datastores
  • Companies may receive dozens or hundreds of requests a day

Unfortunately for affected businesses, there is no "standard" CCPA-compliance tool, because every company stores customer data differently (different databases, logging, etc) — so, every company must implement a custom workflow.

CloudWright helps companies build these workflows. Because CloudWright excels at aggregating data across a company's resources and provides rich options for programmatic interaction, it's easy to build a reporting workflow for your specific data infrastructure.

This article shows how to automate a simple CCPA 'right to request' workflow in CloudWright, interacting with the compliance team via a low-friction Slack app.

Workflow Overview

The beginning of a CCPA right-of-request workflow begins with a user request. We won't cover the user-interaction component — we'll assume our UI solution collects a name, email, and photo ID for verification.

The workflow we'll implement has four steps:

  1. A CloudWright application will format the request as a Slack prompt for the compliance team
  2. The compliance team will evaluate the request and either approve or deny the request within Slack
  3. The Slack response will trigger a report generator, which will build a report from internal data
  4. The requesting user will be emailed either the generated report or a rejection message

Or, graphically:

CloudWright CCPA Flow

Let's get started.


Our workflow will interact with three services — MySQL, Gmail, and Slack. Our applications will interact with these services using Modules. A module is a configure-once, use everywhere integration to a specific internal or external service, packaging client libraries alongside configurations and secrets

You can find more information on creating and using Modules in the CloudWright docs.

Building the Applications

We'll create two CloudWright Applications for this workflow: a Message Generator which translates requests into Slack messages and a Report Generator which receives Slack actions, builds a report, and emails it to the requester.

Message generator

Our Slack message generator application will be very simple — we'll use Slack blocks to provide a simple UI where our compliance team can either approve, or reject requests. This application will only use one module — a 'Slack API' with a compliance-bot Slack app.

We'll cover the important bits in this article — you can find the whole application here.

Our Application will take three inputs: name, email, and a link the uploaded photo ID. We'll attach to our Slack integration to send messages:

name = CloudWright.inputs.get('ccpa_name')
email = CloudWright.inputs.get('ccpa_email')
license_link = CloudWright.inputs.get('ccpa_license_link')

slack = CloudWright.get_module("slack")

The rest of the application does three things:

  • Encode our input to pass to the report generator
  • Format a Slack message (you can view the message template above)
  • Send a Slack message to the compliance channel
value = base64.b64encode(json.dumps({
    'name': name,
    'email': email,
    'license_link': license_link

msg = message_template(name, email, license_link, value)
slack.chat_postMessage(channel='bots', blocks=msg)

The last thing we'll want to do is create an HTTP Trigger so our user-facing component can programatically invoke this application.

If we run a test request through our application, we get a nicely-formatted prompt in our 'bots' Slack channel:

Test Message

We're good to go — let's build the report generator.

Report generator

We'll cover the important parts of the report generator here, but you can find the whole application here. This application will use all three modules we configured earlier — MySQL, Gmail, and Slack.

The Slack Module, as well as providing an API for sending messages, helps us authenticate webhook calls from Slack (see 'Setting up the Modules' for details about how CloudWright authenticates with Slack):

slack = CloudWright.get_module("slack")
if slack.cloudwright_validate_request():

We'll extract the user action from the Slack action payload ('Approve' or 'Reject'):

approve = action.get('text').get('text').strip()

Now we can get to the meat of the application — for valid requests, we look up the user's data in the attached database. In this (simple) example, we only have one data field about a user; in practice, this data might aggregate hundreds of fields across several databases:

db = CloudWright.get_module("mysql")
if action is 'Reject':
    return 'Export rejected.  Send a better picture.'
    # Look up the user's data and format a reply. In practice, this logic will
    # be more complex, likely querying logs or several databases. 
    query = sqlalchemy.text("SELECT name,favorite_food FROM users \
                 WHERE email=:email")
    record = db.execute(query, email=email).fetchone()
    if record:
        return f"Here's what we know:<br/> \
            <br/>Your name: {record[0]} \
            <br/>Your favorite food: {record[1]}"
        return "We don't have any data about you."

Once we have a formatted message, we'll use our Gmail module to send it to the original requester. In practice, we may want to format and attach a PDF; here, we'll just paste it in the body of the email:

emailer = CloudWright.get_module("gmail")
emailer.send_email('Your CCPA data export', message, email)

The Slack message's payload contains a helpful response_url we can call to mark the request as complete. We'll collapse the original prompt so we won't accidentally send the same report twice:

res ='response_url'), json={
    "replace_original": "true", 
    "text": name +": " +approve+" :white_check_mark:"

The last thing we'll need to do is connect our app to Slack. To do this we'll create an HTTP Trigger. We can use the generated endpoint as our Slack app's Request URL — this will be the URL Slack calls when users interact with dialogs.

Once we publish our application, we're good to go — let's run the entire flow.

Testing the workflow

We can use the HTTP endpoint we created on the request generator application to submit a data export request with our test data (Snoopy,, and a convincing photo).

This sends a message to our compliance team's Slack channel, prompting them to either approve or reject the request. Our compliance team decides that the attached photo ID checks out as completely legit, and chooses to approve the report:

Since we sent this email to a test address, we can verify that our exported data was sent to the reporter's email address, with all relevant 'Right of Access' data attached:

CCPA Email

Last but not least, we can jump over to the Run Console and see that our exported requests are showing up in the application history. This gives us a good audit trail, as well as confidence that our compliance workflow is running:

Run history


While this example is a simplified version of the compliance workflows that CCPA-affected companies are building, it shows how easy it is to use plug-and-play CloudWright modules and API integrations to build a robust, easy-to-use compliance pipeline.

If you're struggling to implement your own compliance workflows and want to use CloudWright to automate your manual processes, get started with CloudWright today.