Back to GDPR / Data Privacy Request Handling

Developer Handover Pack

Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.

4 pagesPDF · Technical
FS-DOC-04Technical

Developer Handover Pack

GDPR / Data Privacy Request Handling

[YourCompany.com] · Legal Department · Prepared by FullSpec · [Today's Date]

This document gives the FullSpec build team everything needed to configure, connect, and test the GDPR and data privacy request handling automation from first trigger to closed audit log. It covers the full current-state step map with time costs, both agent specifications with IO contracts and implementation constraints, the end-to-end data flow as a traceable code block, and the recommended build stack with total effort estimate. The process owner's role is limited to confirming API access, approving draft responses in Notion, and accepting the handover once QA is complete. FullSpec builds, tests, and monitors all automation logic.

01Process snapshot

Step
Name
Description
1
Receive and Acknowledge Request
Operations Manager reads incoming request from email, form, or chat and sends a manual acknowledgment. No centralised log exists at this point. Time cost: 15 min.
2
Log Request in Tracker
Operations Manager creates a row in Notion recording requester name, date received, request type, and deadline. This step is often delayed or skipped under workload. Time cost: 10 min.
3
Verify Requester Identity
BOTTLENECK. Operations Manager emails the requester asking for proof of identity and waits for a reply. The 30-day clock is running but processing cannot proceed. Time cost: 20 min plus variable wait.
4
Confirm Identity Documents Received
Operations Manager reviews the returned ID documents manually and notes the verification outcome in the tracker. Unclear documents require another email round. Time cost: 15 min.
5
Search CRM for Held Data
BOTTLENECK. Operations Manager manually searches HubSpot for all records linked to the requester: contacts, deals, notes, and email history. Each category checked individually. Time cost: 45 min.
6
Gather Data From Other Systems
BOTTLENECK. Additional data from billing, support, or other platforms is pulled manually and compiled alongside the CRM export. Most time-consuming and error-prone step. Time cost: 60 min.
7
Compile Response Package
Operations Manager assembles gathered data into a structured document or email response meeting the legal standard for the request type. Time cost: 40 min.
8
Legal or Management Review
Draft response forwarded to Director or Legal Adviser for sign-off. Scheduling this review frequently introduces delays. Time cost: 30 min.
9
Send Response to Requester
Approved response sent to requester by email along with any required data pack or deletion confirmation. Time cost: 10 min.
10
Update Audit Log and Close Request
Operations Manager updates the tracker with response date, outcome, and notes, then marks the request as closed. Frequently forgotten until a compliance review. Time cost: 10 min.
Time cost summary: Total manual time per request cycle is 255 minutes (4 hours 15 min). At approximately 4 requests per month, this equates to roughly 17 hours per month or 3.5 hours per week of Operations Manager and Legal Adviser time. Steps 1, 2, and 3 (acknowledge, log, and verify) are replaced by the Intake and Triage Agent. Steps 4, 5, 6, and 7 (confirm ID, CRM search, multi-system data gather, and draft compile) are replaced by the Data Discovery Agent. Steps 8, 9, and 10 (review gate, send, and close) remain in the workflow but are structurally supported and partially automated: the approval gate is enforced by the platform, and steps 9 and 10 execute automatically after the reviewer approves in Notion.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Intake and Triage Agent

Receives a structured Typeform submission the moment a data subject submits a privacy request. The agent classifies the request type (Subject Access Request, erasure, or rectification), creates a structured case record in Notion with the submission timestamp and calculated 30-day deadline, and dispatches a templated identity verification email via Gmail, all without any manual input. This agent is the entry point for every case and is responsible for the completeness and accuracy of the initial Notion record, which becomes the single source of truth for the entire workflow.

Trigger
New Typeform submission received on the privacy request intake form
Tools
Typeform (webhook trigger), Notion (database write), Gmail (outbound email via OAuth)
Replaces steps
Steps 1, 2, and 3: Receive and Acknowledge Request, Log Request in Tracker, Verify Requester Identity
Estimated build
14 hours, Moderate complexity
// Input (from Typeform webhook payload)
form_response.answers.requester_full_name       // string
form_response.answers.requester_email           // string, validated email format
form_response.answers.request_type             // enum: SAR | erasure | rectification
form_response.answers.additional_context       // string, optional free text
form_response.submitted_at                     // ISO 8601 timestamp

// Output (written to Notion case record)
notion.case_id                                  // auto-generated Notion page ID
notion.requester_name                           // mapped from requester_full_name
notion.requester_email                          // mapped from requester_email
notion.request_type                             // mapped from request_type
notion.submission_date                          // mapped from submitted_at
notion.deadline_date                            // submitted_at + 30 calendar days
notion.identity_status                          // default: 'Pending'
notion.case_status                              // default: 'Open'

// Output (Gmail verification email)
gmail.to                                        // requester_email
gmail.subject                                   // 'Your privacy request has been received - action required'
gmail.body                                      // templated: includes request_type, deadline_date, ID requirements
gmail.sent_timestamp                            // recorded back to Notion case record
  • Typeform must be on a paid plan (Basic or above) to enable webhook delivery. Confirm the workspace admin can create a new form and activate webhook publishing before build starts.
  • The Notion integration token must have 'Insert content', 'Read content', and 'Update content' permissions on the target database. The database must include the following properties at minimum: Case ID (title), Requester Name (text), Requester Email (email), Request Type (select), Submission Date (date), Deadline Date (date), Identity Status (select: Pending, Confirmed, Failed), Case Status (select: Open, In Progress, Pending Review, Closed), Verification Email Sent (checkbox), Sent Timestamp (date).
  • Gmail authentication must use OAuth 2.0 with the gmail.send scope. A dedicated service account or shared mailbox (for example privacy@[YourCompany.com]) is strongly recommended so the sender address is consistent and auditable.
  • The verification email template must be finalised and approved by the Legal Adviser before build. The template must reference the specific request type dynamically so that SAR, erasure, and rectification requests receive appropriately worded emails.
  • Deadline calculation must account for calendar days, not business days, in line with GDPR Article 12. The platform must compute submission_date + 30 days precisely and store the result in Notion before any further steps run.
  • Dedupe behaviour: if a Typeform submission is received with a requester_email that already has an Open or In Progress case record in Notion (created within the last 60 days), the agent must flag the duplicate to the Operations Manager via Slack rather than creating a second record. It must not suppress the submission silently.
  • Fallback: if the Notion write fails, the agent must retain the Typeform payload and retry up to three times with exponential backoff (30 s, 2 min, 5 min). If all retries fail, an alert email must be sent to the process owner and the error logged to the error table.
Data Discovery Agent

Activates once a reviewer marks the Identity Status field in the Notion case record as 'Confirmed'. The agent queries HubSpot via its API to retrieve all records associated with the requester, including contact properties, deal history, associated notes, and logged email history. It then compiles the retrieved data into a structured draft response package within the Notion case record, formats it according to the request type (SAR data export, erasure confirmation, or rectification summary), and sends a Slack notification to the assigned reviewer with a direct link to the case record and approval prompt. The agent does not send any external communication and does not proceed beyond Slack notification without explicit reviewer approval logged in Notion.

Trigger
Identity Status field in the Notion case record is updated to 'Confirmed' (watched via Notion API polling or webhook on database change)
Tools
Notion (read trigger and draft write), HubSpot (CRM API query via private app token), Slack (outbound notification to reviewer channel)
Replaces steps
Steps 4, 5, 6, and 7: Confirm Identity Documents Received, Search CRM for Held Data, Gather Data From Other Systems, Compile Response Package
Estimated build
16 hours, Moderate complexity
// Input (read from Notion case record on trigger)
notion.case_id                                  // page ID of the active case
notion.requester_name                           // used as HubSpot search parameter
notion.requester_email                          // primary HubSpot lookup key
notion.request_type                             // SAR | erasure | rectification
notion.deadline_date                            // surfaced in Slack notification
notion.identity_status                          // must equal 'Confirmed' before execution

// HubSpot API queries (private app token, v3 CRM API)
GET /crm/v3/objects/contacts?email={requester_email}   // resolve contact ID
GET /crm/v3/objects/contacts/{contact_id}?properties=  // all contact properties
GET /crm/v3/objects/deals?associations.contact={id}    // associated deals
GET /crm/v3/objects/notes?associations.contact={id}    // associated notes
GET /crm/v3/objects/emails?associations.contact={id}   // logged email history

// Output (written to Notion case record as draft response block)
notion.hubspot_contact_properties               // JSON snapshot stored in case page
notion.hubspot_deals_summary                   // list of deal names, stages, values
notion.hubspot_notes_log                        // list of note bodies and dates
notion.hubspot_email_log                        // list of email subjects and timestamps
notion.draft_response_status                   // set to 'Awaiting Review'
notion.data_retrieved_timestamp                // ISO 8601 timestamp of retrieval

// Output (Slack notification to reviewer)
slack.channel                                   // #privacy-reviews or DM to assigned reviewer
slack.message                                   // includes case_id, requester_name, request_type, deadline_date, Notion case URL
slack.sent_timestamp                            // recorded back to Notion case record

// On approval (reviewer sets Notion approval field to 'Approved')
gmail.to                                        // requester_email
gmail.subject                                   // dynamically set by request_type
gmail.body                                      // compiled from notion draft response block
notion.case_status                              // updated to 'Closed'
notion.response_sent_timestamp                 // ISO 8601 timestamp
notion.reviewer_name                            // name of approving reviewer
notion.outcome                                  // SAR sent | Data erased | Data corrected
  • HubSpot integration must use a Private App token, not a legacy API key. The token requires scopes: crm.objects.contacts.read, crm.objects.deals.read, crm.objects.notes.read, crm.objects.emails.read. Confirm the HubSpot account tier supports private apps (Starter and above). The token must be stored in the shared credential store and never hardcoded.
  • Notion polling interval for the Identity Status field change should be set to 60 seconds or the shortest interval the plan supports. If the Notion API supports database-level webhooks (currently in limited beta), use a webhook instead of polling to reduce latency.
  • HubSpot contact lookup must use requester_email as the primary key. If no contact is found, the agent must log a 'No CRM record found' entry in the Notion case record and send a Slack alert to the process owner, then halt execution for that step. It must not proceed to compile a blank response.
  • The draft response structure in Notion must differ by request_type: SAR responses include all retrieved data categories; erasure requests generate a deletion confirmation checklist with fields for each data category; rectification requests surface only the fields the requester has asked to change alongside their current values.
  • The Slack reviewer notification must include a direct URL to the Notion case page and the deadline date. The Slack workspace must have the automation platform's bot app installed with chat:write and channels:read scopes. Confirm the reviewer's Slack user ID or the target channel ID before build.
  • The approval gate logic must be implemented so that the Gmail send step only fires when the Notion 'Reviewer Approval' property is set to 'Approved' by an authenticated Notion user. The platform workflow must poll this field after the Slack notification is sent. If approval is not received within 72 hours of the Slack notification, a reminder Slack message must be sent automatically.
  • Data held outside HubSpot (billing platforms, support tools, offline records) cannot be retrieved automatically at initial rollout. The Notion draft response must include a clearly labelled section titled 'Manual data check required' listing any additional systems the process owner must check before the reviewer approves. This must be confirmed with the process owner before build.
  • Fallback: if the HubSpot API returns a non-200 status code, the agent must retry up to three times, then log the failure to the error table and notify the process owner via Slack. The case must remain in 'In Progress' status until the data retrieval is resolved manually.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow for the GDPR / Data Privacy Request Handling automation
// ── TRIGGER ─────────────────────────────────────────────────────────────────
// Event: Typeform 'Privacy Request Intake' form submitted
TYPEFORM_WEBHOOK_PAYLOAD {
  form_response.answers.requester_full_name     // string
  form_response.answers.requester_email         // string
  form_response.answers.request_type           // 'SAR' | 'erasure' | 'rectification'
  form_response.answers.additional_context     // string, optional
  form_response.submitted_at                   // ISO 8601
}

// ── AGENT 1: INTAKE AND TRIAGE AGENT ────────────────────────────────────────
// Step A: Dedupe check against Notion cases database
NOTION_QUERY {
  filter: requester_email == TYPEFORM.requester_email
         AND case_status IN ['Open', 'In Progress']
         AND submission_date >= NOW() - 60 days
  result: if match found -> SLACK_ALERT(duplicate) -> HALT
          if no match   -> CONTINUE
}

// Step B: Create Notion case record
NOTION_DATABASE_CREATE {
  Case ID           <- auto-generated Notion page ID
  Requester Name    <- TYPEFORM.requester_full_name
  Requester Email   <- TYPEFORM.requester_email
  Request Type      <- TYPEFORM.request_type
  Submission Date   <- TYPEFORM.submitted_at
  Deadline Date     <- TYPEFORM.submitted_at + 30 days
  Identity Status   <- 'Pending'
  Case Status       <- 'Open'
}

// Step C: Send identity verification email via Gmail
GMAIL_SEND {
  to       <- TYPEFORM.requester_email
  subject  <- 'Your privacy request has been received - action required'
  body     <- TEMPLATE(request_type, deadline_date, id_requirements)
  on_send  -> NOTION_UPDATE { Verification Email Sent: true, Sent Timestamp: NOW() }
}

// ── HUMAN GATE 1: IDENTITY VERIFICATION ─────────────────────────────────────
// Reviewer examines returned ID documents and updates Notion manually
// NOTION.identity_status set to 'Confirmed' | 'Failed'
// If 'Failed': process owner sends follow-up email (manual exception path)
// If 'Confirmed': triggers Agent 2

// ── AGENT 2: DATA DISCOVERY AGENT ───────────────────────────────────────────
// Trigger: NOTION.identity_status == 'Confirmed' (polling / webhook)

// Step D: Resolve requester in HubSpot
HUBSPOT_API_GET /crm/v3/objects/contacts {
  filter_groups[0].filters[0].propertyName: 'email'
  filter_groups[0].filters[0].value: NOTION.requester_email
  result: contact_id  // if null -> NOTION_UPDATE('No CRM record found') -> SLACK_ALERT -> HALT
}

// Step E: Retrieve all CRM data for contact
HUBSPOT_API_GET /crm/v3/objects/contacts/{contact_id} {
  properties: firstname, lastname, email, phone, createdate, hs_lead_status
  result: contact_properties
}
HUBSPOT_API_GET /crm/v3/objects/deals?associations.contact={contact_id} {
  result: deals_list [ deal_name, deal_stage, amount, closedate ]
}
HUBSPOT_API_GET /crm/v3/objects/notes?associations.contact={contact_id} {
  result: notes_list [ body, createdate, owner ]
}
HUBSPOT_API_GET /crm/v3/objects/emails?associations.contact={contact_id} {
  result: email_log [ subject, sent_at, direction ]
}

// Step F: Compile draft response in Notion
NOTION_PAGE_UPDATE (case_id) {
  hubspot_contact_properties   <- contact_properties (JSON block)
  hubspot_deals_summary        <- deals_list
  hubspot_notes_log            <- notes_list
  hubspot_email_log            <- email_log
  manual_check_section         <- ['Billing system', 'Support platform', 'Offline records']
  draft_response_status        <- 'Awaiting Review'
  data_retrieved_timestamp     <- NOW()
}

// Step G: Notify reviewer via Slack
SLACK_MESSAGE {
  channel  <- #privacy-reviews
  text     <- TEMPLATE(case_id, requester_name, request_type, deadline_date, notion_case_url)
  on_send  -> NOTION_UPDATE { Reviewer Notified Timestamp: NOW() }
}
// If NOTION.reviewer_approval != 'Approved' within 72 hours:
SLACK_MESSAGE { text: 'Reminder: case {case_id} approval still pending. Deadline: {deadline_date}' }

// ── HUMAN GATE 2: REVIEWER APPROVAL ─────────────────────────────────────────
// Reviewer reads Notion draft, annotates if needed, sets Reviewer Approval: 'Approved'
// Platform polls NOTION.reviewer_approval field
// If 'Approved' -> CONTINUE
// If annotated  -> process owner revises draft -> resets field -> re-notifies Slack

// Step H: Send approved response via Gmail
GMAIL_SEND {
  to       <- NOTION.requester_email
  subject  <- TEMPLATE(request_type)  // e.g. 'Your Subject Access Request response'
  body     <- compiled from NOTION draft response block
  on_send  -> NOTION_UPDATE { Response Sent Timestamp: NOW() }
}

// Step I: Close case and update audit log in Notion
NOTION_PAGE_UPDATE (case_id) {
  Case Status              <- 'Closed'
  Response Sent Timestamp  <- NOW()
  Reviewer Name            <- NOTION.last_editor_name
  Outcome                  <- TEMPLATE(request_type)  // 'SAR sent' | 'Data erased' | 'Data corrected'
}

// ── END OF FLOW ──────────────────────────────────────────────────────────────
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation platform with native HTTP request nodes and credential store. One workflow per agent: 'Intake and Triage' and 'Data Discovery'. Both workflows share a single encrypted credential store for all API tokens and OAuth clients. No credentials are stored in workflow node configuration directly.
Webhook configuration
Typeform sends a webhook POST to the platform's unique endpoint URL on every form submission. The endpoint must enforce HTTPS and validate Typeform's HMAC SHA-256 signature header (X-Typeform-Signature) before processing the payload. The Notion trigger for Agent 2 uses polling at 60-second intervals against the cases database, filtering for records where identity_status changed to 'Confirmed' since last poll. If Notion native webhooks become available on the workspace plan, migrate to webhook-based triggering.
Templating approach
All outbound email bodies (Gmail) and Slack messages are stored as named templates within the platform or as Notion template pages. Templates use named variable placeholders (for example {{requester_name}}, {{request_type}}, {{deadline_date}}, {{notion_case_url}}). Template content is finalised with the Legal Adviser before build and stored in version control. Changes to template wording require a re-test of the affected send step before deployment.
Error logging
A dedicated Supabase table ('automation_errors') captures every execution failure. Schema: id (uuid), workflow_name (text), step_name (text), error_message (text), payload_snapshot (jsonb), occurred_at (timestamptz), resolved (boolean). On any caught error the platform writes a row to this table and sends a Slack alert to #automation-alerts tagging the FullSpec on-call contact. Critical failures (Notion write failure, Gmail send failure on approved response) also trigger an email alert to support@gofullspec.com.
Testing approach
All build and integration testing runs in sandbox environments first: Typeform test mode (submissions do not count against quotas), a dedicated Notion test database (separate from the live cases database, identical schema), a Gmail sandbox account (privacy-test@[YourCompany.com]), a HubSpot sandbox portal (free developer account with mirrored contact structure), and a private Slack channel (#privacy-reviews-test). No production credentials are used until QA sign-off. Test runs cover at minimum: SAR request full flow, erasure request full flow, rectification request full flow, duplicate submission detection, HubSpot contact not found path, and reviewer approval reminder trigger.
Credential inventory
Typeform: webhook signing secret (HMAC SHA-256). Notion: internal integration token (read and write on cases database). Gmail: OAuth 2.0 client ID and client secret, refresh token for privacy@[YourCompany.com] (scope: gmail.send). HubSpot: private app token (scopes: crm.objects.contacts.read, crm.objects.deals.read, crm.objects.notes.read, crm.objects.emails.read). Slack: bot OAuth token (scopes: chat:write, channels:read). Supabase: service role key for error logging table. All stored in platform credential store with AES-256 encryption at rest.
Estimated total build time
Intake and Triage Agent: 14 hours. Data Discovery Agent: 16 hours. End-to-end integration testing and QA: 6 hours. Total: 36 hours across a 4-week delivery schedule.
Before build begins, the following must be confirmed: (1) HubSpot private app token issued with the correct read scopes; (2) Notion integration token created and shared with the target cases database; (3) Gmail OAuth consent screen configured for the privacy mailbox; (4) Slack bot app installed in the workspace with required scopes; (5) Typeform paid plan confirmed with webhook publishing enabled; (6) Legal Adviser has approved the identity verification and response email templates; (7) Sandbox environments for all tools are accessible. Raise any access blockers to support@gofullspec.com immediately as these are on the critical path for the 4-week timeline.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for GDPR / Data Privacy Request Handling.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Integration and API Spec
Technical · Developer
View
Test and QA Plan
Quality · Developer
View