Skills Development Klaviyo Data Privacy and Compliance Handling

Klaviyo Data Privacy and Compliance Handling

v20260423
klaviyo-data-handling
A comprehensive solution for managing Personally Identifiable Information (PII) and ensuring compliance with global privacy regulations like GDPR and CCPA within Klaviyo. This skill guides developers on implementing critical functions, including the 'right to erasure' (profile deletion), handling Data Subject Access Requests (DSAR) by exporting all user data, and implementing PII detection and redaction for safe logging practices. It ensures that data handling processes meet legal standards.
Get Skill
267 downloads
Overview

Klaviyo Data Handling

Overview

Handle profile data, PII, and privacy compliance with Klaviyo's Data Privacy API, GDPR right-to-deletion, CCPA requests, and safe logging patterns.

Prerequisites

  • klaviyo-api SDK installed
  • API key with data-privacy:write scope (for deletion requests)
  • Understanding of GDPR/CCPA requirements
  • Audit logging infrastructure

Klaviyo Data Privacy API

Klaviyo provides a dedicated Data Privacy API for GDPR/CCPA profile deletion. When you delete a profile via this API, Klaviyo performs a full GDPR erasure -- the profile is permanently removed and cannot be recovered.

Instructions

Step 1: GDPR Profile Deletion (Right to Erasure)

import { ApiKeySession, DataPrivacyApi } from 'klaviyo-api';

const session = new ApiKeySession(process.env.KLAVIYO_PRIVATE_KEY!);
const dataPrivacyApi = new DataPrivacyApi(session);

/**
 * Request profile deletion via Klaviyo's Data Privacy API.
 * Accepts ONE identifier: email, phone_number, or profile ID.
 * Providing multiple identifiers returns an error.
 *
 * WARNING: This is irreversible. Profile is permanently erased.
 */
async function requestProfileDeletion(identifier: {
  email?: string;
  phoneNumber?: string;
  profileId?: string;
}): Promise<void> {
  // Build the profile identifier (only ONE allowed)
  const profileData: any = { type: 'profile' };

  if (identifier.email) {
    profileData.attributes = { email: identifier.email };
  } else if (identifier.phoneNumber) {
    profileData.attributes = { phone_number: identifier.phoneNumber };
  } else if (identifier.profileId) {
    profileData.id = identifier.profileId;
  } else {
    throw new Error('Must provide exactly one identifier: email, phoneNumber, or profileId');
  }

  await dataPrivacyApi.requestProfileDeletion({
    data: {
      type: 'data-privacy-deletion-job',
      attributes: {
        profile: { data: profileData },
      },
    },
  });

  // Audit log (required for compliance)
  await auditLog({
    action: 'GDPR_DELETION_REQUESTED',
    identifier: identifier.email || identifier.phoneNumber || identifier.profileId!,
    service: 'klaviyo',
    timestamp: new Date().toISOString(),
  });

  console.log(`Deletion requested for ${JSON.stringify(identifier)}`);
}

// Usage
await requestProfileDeletion({ email: 'user-wants-deletion@example.com' });

Step 2: Data Subject Access Request (DSAR)

import { ProfilesApi, EventsApi, ListsApi } from 'klaviyo-api';

/**
 * Export all Klaviyo data for a given profile (GDPR Article 15).
 * Returns all profile attributes, event history, and list memberships.
 */
async function exportProfileData(email: string): Promise<{
  profile: any;
  events: any[];
  lists: any[];
}> {
  const profilesApi = new ProfilesApi(session);
  const eventsApi = new EventsApi(session);

  // 1. Get profile
  const profiles = await profilesApi.getProfiles({
    filter: `equals(email,"${email}")`,
  });
  const profile = profiles.body.data[0];
  if (!profile) throw new Error(`No profile found for ${email}`);

  // 2. Get profile's events
  const events = await eventsApi.getEvents({
    filter: `equals(profile_id,"${profile.id}")`,
    sort: '-datetime',
  });

  // 3. Get profile's list memberships
  const profileLists = await profilesApi.getProfileLists({ id: profile.id });

  return {
    profile: {
      id: profile.id,
      ...profile.attributes,
    },
    events: events.body.data.map(e => ({
      metric: e.attributes.metricId,
      datetime: e.attributes.datetime,
      properties: e.attributes.eventProperties,
    })),
    lists: profileLists.body.data.map(l => ({
      id: l.id,
      name: l.attributes.name,
    })),
  };
}

Step 3: PII Detection and Redaction in Logs

// src/klaviyo/pii.ts

const PII_PATTERNS = [
  { type: 'email', regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g },
  { type: 'phone', regex: /\+?\d{10,15}/g },
  { type: 'api_key', regex: /pk_[a-zA-Z0-9]{20,}/g },
];

export function redactPII(text: string): string {
  let redacted = text;
  for (const pattern of PII_PATTERNS) {
    redacted = redacted.replace(pattern.regex, `[REDACTED:${pattern.type}]`);
  }
  return redacted;
}

export function redactObject(data: Record<string, any>): Record<string, any> {
  const sensitiveFields = ['email', 'phoneNumber', 'phone_number', 'firstName', 'lastName', 'apiKey'];
  const redacted = { ...data };

  for (const field of sensitiveFields) {
    if (redacted[field]) {
      redacted[field] = typeof redacted[field] === 'string'
        ? `${redacted[field].substring(0, 3)}***`
        : '[REDACTED]';
    }
  }

  return redacted;
}

// Usage: safe logging of Klaviyo API responses
console.log('Profile data:', redactObject(profile.attributes));

Step 4: Consent Management

/**
 * Record consent and subscribe to marketing.
 * Always include consent timestamp for GDPR compliance.
 */
async function recordConsent(
  email: string,
  channels: { email?: boolean; sms?: boolean },
  listId: string,
  consentSource: string
): Promise<void> {
  const subscriptions: any = {};
  const now = new Date().toISOString();

  if (channels.email) {
    subscriptions.email = {
      marketing: { consent: 'SUBSCRIBED', consentTimestamp: now },
    };
  }
  if (channels.sms) {
    subscriptions.sms = {
      marketing: { consent: 'SUBSCRIBED', consentTimestamp: now },
    };
  }

  await profilesApi.subscribeProfiles({
    data: {
      type: 'profile-subscription-bulk-create-job',
      attributes: {
        profiles: {
          data: [{
            type: 'profile' as any,
            attributes: {
              email,
              subscriptions,
            },
          }],
        },
        historicalImport: false,  // Set true for pre-existing consent
      },
      relationships: {
        list: { data: { type: 'list', id: listId } },
      },
    },
  });

  await auditLog({
    action: 'CONSENT_RECORDED',
    identifier: email,
    channels: Object.keys(channels).filter(c => channels[c as keyof typeof channels]),
    source: consentSource,
    timestamp: now,
  });
}

Step 5: Audit Logging

// src/klaviyo/audit.ts

interface AuditEntry {
  action: string;
  identifier: string;
  service: string;
  timestamp: string;
  details?: Record<string, any>;
}

async function auditLog(entry: AuditEntry): Promise<void> {
  // Write to your audit database (must be retained per GDPR)
  await db.auditLog.create({
    data: {
      ...entry,
      retainUntil: new Date(Date.now() + 7 * 365 * 24 * 60 * 60 * 1000), // 7 years
    },
  });

  console.log(`[AUDIT] ${entry.action}: ${entry.identifier} at ${entry.timestamp}`);
}

Data Classification for Klaviyo

Data Category Examples in Klaviyo Handling
PII email, phoneNumber, firstName, lastName Redact in logs, encrypt at rest
Sensitive API keys, webhook secrets Never log, rotate quarterly
Behavioral Events, page views, purchases Anonymize where possible
Marketing List memberships, consent status Audit trail required
Derived Segments, predictive analytics No special handling

Error Handling

Issue Cause Solution
Deletion request fails Missing data-privacy:write scope Update API key scopes
Multiple identifiers error Providing email AND phone Use exactly one identifier
Profile not found for DSAR Wrong email or already deleted Search by ID or phone instead
PII in error logs Unredacted API responses Wrap logger with redactObject()

Resources

Next Steps

For enterprise access control, see klaviyo-enterprise-rbac.

Info
Category Development
Name klaviyo-data-handling
Version v20260423
Size 8.57KB
Updated At 2026-04-28
Language