Skip to content

Integration Guide: Third-Party Services

This guide demonstrates how to securely integrate the Grizzly SDK with third-party services while maintaining encryption standards. You'll learn patterns for encrypting data before sending to external APIs, decrypting webhooks, and implementing secure data sharing workflows.

Overview

Third-party integrations with Grizzly provide:

  • Encrypt before sending - Protect sensitive data in transit to external services
  • Decrypt webhooks - Securely receive encrypted data from partners
  • Audit trail - Track all external data sharing in Activity feed
  • Compliance - Meet GDPR, HIPAA data sharing requirements
  • Key rotation - Seamless key updates without disrupting integrations

Payment Processor Integration

Stripe with Encrypted Customer Data

When integrating with payment processors like Stripe, you can encrypt sensitive customer data before storing it alongside Stripe's payment methods.

import { KeyClient } from '@grizzlycbg/grizzly-sdk'
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)

const grizzlyClient = await KeyClient.create({
   host: 'https://your-tenant.grizzlycbg.io',
   apikey: process.env.GRIZZLY_API_KEY
})

class SecurePaymentService {
   constructor(grizzlyClient, stripe, keyringName = 'PaymentData') {
      this.client = grizzlyClient
      this.stripe = stripe
      this.keyringName = keyringName
   }

   async createCustomerWithEncryptedData(customerData) {
      const ring = await this.client.ring.get(this.keyringName)

      // Encrypt PII before storing in Stripe metadata
      const encryptedSSN = customerData.ssn ? await ring.encrypt(customerData.ssn, {
         asset: {
            id: `customer-${customerData.email}`,
            name: `${customerData.name} - SSN`,
            type: 'pii',
            field: 'ssn',
            processor: 'stripe'
         }
      }) : null

      const encryptedAddress = await ring.encrypt(JSON.stringify(customerData.address), {
         asset: {
            id: `customer-${customerData.email}`,
            name: `${customerData.name} - Address`,
            type: 'pii',
            field: 'address',
            processor: 'stripe'
         }
      })

      // Create Stripe customer with encrypted metadata
      const stripeCustomer = await this.stripe.customers.create({
         email: customerData.email,
         name: customerData.name,
         metadata: {
            // Store encrypted data in metadata (not visible to Stripe)
            encrypted_ssn: encryptedSSN || '',
            encrypted_address: encryptedAddress,
            grizzly_keyring: this.keyringName
         }
      })

      console.log(`Stripe customer created: ${stripeCustomer.id}`)

      return {
         stripeCustomerId: stripeCustomer.id,
         email: stripeCustomer.email,
         name: stripeCustomer.name
      }
   }

   async getCustomerWithDecryptedData(stripeCustomerId) {
      // Retrieve from Stripe
      const stripeCustomer = await this.stripe.customers.retrieve(stripeCustomerId)

      // Decrypt metadata
      const decryptedData = {
         stripeCustomerId: stripeCustomer.id,
         email: stripeCustomer.email,
         name: stripeCustomer.name,
         ssn: null,
         address: null
      }

      if (stripeCustomer.metadata.encrypted_ssn) {
         decryptedData.ssn = await this.client.decrypt(stripeCustomer.metadata.encrypted_ssn)
      }

      if (stripeCustomer.metadata.encrypted_address) {
         const addressJson = await this.client.decrypt(stripeCustomer.metadata.encrypted_address)
         decryptedData.address = JSON.parse(addressJson)
      }

      return decryptedData
   }

   async createPaymentIntent(amount, currency, stripeCustomerId, paymentMetadata = {}) {
      const ring = await this.client.ring.get(this.keyringName)

      // Encrypt payment metadata
      const encryptedMetadata = await ring.encrypt(JSON.stringify(paymentMetadata), {
         asset: {
            id: `payment-${Date.now()}`,
            name: `Payment - ${stripeCustomerId}`,
            type: 'payment',
            field: 'metadata',
            processor: 'stripe',
            amount,
            currency
         }
      })

      const paymentIntent = await this.stripe.paymentIntents.create({
         amount,
         currency,
         customer: stripeCustomerId,
         metadata: {
            encrypted_metadata: encryptedMetadata
         }
      })

      return paymentIntent
   }
}

// Usage
const paymentService = new SecurePaymentService(grizzlyClient, stripe)

// Create customer with encrypted PII
const customer = await paymentService.createCustomerWithEncryptedData({
   email: 'customer@example.com',
   name: 'Jane Smith',
   ssn: '123-45-6789',
   address: {
      street: '123 Main St',
      city: 'San Francisco',
      state: 'CA',
      zip: '94102'
   }
})

// Retrieve and decrypt customer data
const decryptedCustomer = await paymentService.getCustomerWithDecryptedData(customer.stripeCustomerId)
console.log(decryptedCustomer.address)  // { street: '123 Main St', ... }

// Create payment with encrypted metadata
const payment = await paymentService.createPaymentIntent(
   5000,  // $50.00
   'usd',
   customer.stripeCustomerId,
   {
      orderId: 'order-789',
      productId: 'prod-456',
      customNotes: 'Gift wrapping requested'
   }
)

Cloud Storage Integration

AWS S3 with Client-Side Encryption

Encrypt files before uploading to cloud storage services like AWS S3.

import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'
import { KeyClient } from '@grizzlycbg/grizzly-sdk'
import { Readable } from 'stream'
import fs from 'fs'

class SecureCloudStorage {
   constructor(grizzlyClient, s3Config, keyringName = 'CloudStorage') {
      this.grizzlyClient = grizzlyClient
      this.s3Client = new S3Client(s3Config)
      this.keyringName = keyringName
   }

   async uploadEncrypted(bucket, key, filePath, metadata = {}) {
      const ring = await this.grizzlyClient.ring.get(this.keyringName)

      // Create file stream
      const fileStream = fs.createReadStream(filePath)
      const fileName = filePath.split('/').pop()

      // Encrypt stream
      const encryptedStream = await ring.encrypt(fileStream, {
         asset: {
            id: `s3-${bucket}-${key}`,
            name: fileName,
            type: 'file',
            field: 'content',
            storage: 's3',
            bucket,
            key,
            ...metadata
         }
      })

      // Convert stream to buffer for S3 upload
      const chunks = []
      for await (const chunk of encryptedStream) {
         chunks.push(chunk)
      }
      const encryptedBuffer = Buffer.concat(chunks)

      // Upload to S3
      const command = new PutObjectCommand({
         Bucket: bucket,
         Key: key,
         Body: encryptedBuffer,
         Metadata: {
            'grizzly-encrypted': 'true',
            'grizzly-keyring': this.keyringName,
            'original-filename': fileName
         }
      })

      await this.s3Client.send(command)

      console.log(`File uploaded and encrypted: s3://${bucket}/${key}`)

      return {
         bucket,
         key,
         size: encryptedBuffer.length,
         encrypted: true
      }
   }

   async downloadDecrypted(bucket, key, outputPath) {
      // Download from S3
      const command = new GetObjectCommand({
         Bucket: bucket,
         Key: key
      })

      const response = await this.s3Client.send(command)

      // Check if file is Grizzly-encrypted
      if (response.Metadata?.['grizzly-encrypted'] !== 'true') {
         throw new Error('File is not Grizzly-encrypted')
      }

      // Convert S3 stream to buffer
      const chunks = []
      for await (const chunk of response.Body) {
         chunks.push(chunk)
      }
      const encryptedBuffer = Buffer.concat(chunks)

      // Decrypt
      const decrypted = await this.grizzlyClient.decrypt(encryptedBuffer)

      // Write to file
      fs.writeFileSync(outputPath, decrypted)

      console.log(`File downloaded and decrypted: ${outputPath}`)

      return {
         bucket,
         key,
         outputPath,
         size: decrypted.length
      }
   }

   async uploadStreamEncrypted(bucket, key, readableStream, metadata = {}) {
      const ring = await this.grizzlyClient.ring.get(this.keyringName)

      // Encrypt stream
      const encryptedStream = await ring.encrypt(readableStream, {
         asset: {
            id: `s3-${bucket}-${key}`,
            name: key,
            type: 'stream',
            storage: 's3',
            bucket,
            key,
            ...metadata
         }
      })

      // Stream directly to S3 (for large files)
      const command = new PutObjectCommand({
         Bucket: bucket,
         Key: key,
         Body: encryptedStream,
         Metadata: {
            'grizzly-encrypted': 'true',
            'grizzly-keyring': this.keyringName
         }
      })

      await this.s3Client.send(command)

      console.log(`Stream uploaded and encrypted: s3://${bucket}/${key}`)
   }
}

// Usage
const s3Storage = new SecureCloudStorage(grizzlyClient, {
   region: 'us-west-2',
   credentials: {
      accessKeyId: process.env.AWS_ACCESS_KEY_ID,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
   }
})

// Upload and encrypt file
await s3Storage.uploadEncrypted(
   'my-secure-bucket',
   'documents/contract-2024.pdf',
   './contract.pdf',
   {
      department: 'legal',
      classification: 'confidential'
   }
)

// Download and decrypt file
await s3Storage.downloadDecrypted(
   'my-secure-bucket',
   'documents/contract-2024.pdf',
   './contract-decrypted.pdf'
)

Webhook Integration

Secure Webhook Data Exchange

Exchange encrypted data with partners via webhooks.

import express from 'express'
import { KeyClient } from '@grizzlycbg/grizzly-sdk'
import crypto from 'crypto'

const app = express()
app.use(express.json())

const grizzlyClient = await KeyClient.create({
   host: 'https://your-tenant.grizzlycbg.io',
   apikey: process.env.GRIZZLY_API_KEY
})

class SecureWebhookHandler {
   constructor(grizzlyClient, keyringName = 'Webhooks') {
      this.client = grizzlyClient
      this.keyringName = keyringName
   }

   // Send encrypted webhook to partner
   async sendWebhook(url, data, secret) {
      const ring = await this.client.ring.get(this.keyringName)

      // Encrypt payload
      const encryptedPayload = await ring.encrypt(JSON.stringify(data), {
         asset: {
            id: `webhook-${Date.now()}`,
            name: `Webhook to ${url}`,
            type: 'webhook',
            field: 'payload',
            direction: 'outbound',
            url
         }
      })

      // Create signature for verification
      const signature = crypto
         .createHmac('sha256', secret)
         .update(encryptedPayload)
         .digest('hex')

      // Send webhook
      const response = await fetch(url, {
         method: 'POST',
         headers: {
            'Content-Type': 'application/json',
            'X-Grizzly-Signature': signature,
            'X-Grizzly-Encrypted': 'true'
         },
         body: JSON.stringify({
            encrypted: true,
            payload: encryptedPayload,
            timestamp: Date.now()
         })
      })

      console.log(`Webhook sent: ${response.status}`)

      return response
   }

   // Receive and decrypt webhook from partner
   async receiveWebhook(req, secret) {
      // Verify signature
      const signature = req.headers['x-grizzly-signature']
      const body = req.body

      if (!signature) {
         throw new Error('Missing signature header')
      }

      const expectedSignature = crypto
         .createHmac('sha256', secret)
         .update(body.payload)
         .digest('hex')

      if (signature !== expectedSignature) {
         throw new Error('Invalid signature')
      }

      // Decrypt payload
      const decryptedPayload = await this.client.decrypt(body.payload)
      const data = JSON.parse(decryptedPayload)

      console.log('Webhook received and decrypted')

      return data
   }

   // Middleware for Express
   createExpressMiddleware(secret) {
      return async (req, res, next) => {
         try {
            if (req.headers['x-grizzly-encrypted'] === 'true') {
               req.grizzlyData = await this.receiveWebhook(req, secret)
            }
            next()
         } catch (error) {
            res.status(401).json({ error: 'Webhook verification failed' })
         }
      }
   }
}

// Usage
const webhookHandler = new SecureWebhookHandler(grizzlyClient)

// Send encrypted webhook
await webhookHandler.sendWebhook(
   'https://partner.example.com/webhooks/data',
   {
      userId: 'user-123',
      event: 'order.completed',
      orderDetails: {
         orderId: 'order-789',
         amount: 99.99,
         items: ['item-1', 'item-2']
      }
   },
   process.env.WEBHOOK_SECRET
)

// Receive encrypted webhook (Express route)
const webhookMiddleware = webhookHandler.createExpressMiddleware(process.env.WEBHOOK_SECRET)

app.post('/webhooks/partner-data', webhookMiddleware, (req, res) => {
   const data = req.grizzlyData

   console.log('Received webhook data:', data)

   // Process the decrypted data
   // ...

   res.json({ received: true })
})

app.listen(3000, () => {
   console.log('Webhook server listening on port 3000')
})

Analytics Platform Integration

Send Encrypted Events to Analytics

Track user behavior while keeping PII encrypted when sending to analytics platforms.

import { KeyClient } from '@grizzlycbg/grizzly-sdk'
import Analytics from 'analytics-node'

class SecureAnalytics {
   constructor(grizzlyClient, segmentWriteKey, keyringName = 'Analytics') {
      this.grizzlyClient = grizzlyClient
      this.analytics = new Analytics(segmentWriteKey)
      this.keyringName = keyringName
   }

   async trackEvent(userId, event, properties = {}) {
      const ring = await this.grizzlyClient.ring.get(this.keyringName)

      // Identify PII fields to encrypt
      const piiFields = ['email', 'name', 'phone', 'address', 'ssn']
      const encryptedProperties = { ...properties }

      // Encrypt PII fields
      for (const field of piiFields) {
         if (properties[field]) {
            encryptedProperties[field] = await ring.encrypt(properties[field], {
               asset: {
                  id: `analytics-${userId}-${event}`,
                  name: `Analytics Event - ${event}`,
                  type: 'analytics',
                  field,
                  userId,
                  event
               }
            })
            // Mark as encrypted for later identification
            encryptedProperties[`${field}_encrypted`] = true
         }
      }

      // Send to analytics platform with encrypted PII
      this.analytics.track({
         userId,
         event,
         properties: encryptedProperties,
         context: {
            grizzly: {
               encrypted: true,
               keyring: this.keyringName
            }
         }
      })

      console.log(`Analytics event tracked: ${event}`)
   }

   async identifyUser(userId, traits = {}) {
      const ring = await this.grizzlyClient.ring.get(this.keyringName)

      // Encrypt user traits
      const encryptedTraits = {}

      for (const [key, value] of Object.entries(traits)) {
         if (['email', 'name', 'phone', 'address'].includes(key)) {
            encryptedTraits[key] = await ring.encrypt(value, {
               asset: {
                  id: `analytics-user-${userId}`,
                  name: `User ${userId} - ${key}`,
                  type: 'analytics',
                  field: key,
                  userId
               }
            })
            encryptedTraits[`${key}_encrypted`] = true
         } else {
            // Non-PII can be sent as-is
            encryptedTraits[key] = value
         }
      }

      this.analytics.identify({
         userId,
         traits: encryptedTraits
      })

      console.log(`User identified: ${userId}`)
   }

   async flush() {
      return new Promise((resolve, reject) => {
         this.analytics.flush((err) => {
            if (err) reject(err)
            else resolve()
         })
      })
   }
}

// Usage
const analytics = new SecureAnalytics(
   grizzlyClient,
   process.env.SEGMENT_WRITE_KEY
)

// Track event with encrypted PII
await analytics.trackEvent('user-123', 'Purchase Completed', {
   orderId: 'order-789',
   amount: 99.99,
   email: 'customer@example.com',  // Will be encrypted
   name: 'John Doe',               // Will be encrypted
   product: 'Widget Pro'            // Not PII, sent as-is
})

// Identify user with encrypted traits
await analytics.identifyUser('user-123', {
   email: 'customer@example.com',   // Encrypted
   name: 'John Doe',                // Encrypted
   phone: '555-1234',               // Encrypted
   plan: 'enterprise',              // Not encrypted
   signupDate: '2024-01-15'        // Not encrypted
})

await analytics.flush()

CRM Integration

Salesforce with Encrypted Custom Fields

Store encrypted sensitive data in Salesforce custom fields.

import { KeyClient } from '@grizzlycbg/grizzly-sdk'
import jsforce from 'jsforce'

class SecureSalesforceIntegration {
   constructor(grizzlyClient, salesforceConfig, keyringName = 'CRM') {
      this.grizzlyClient = grizzlyClient
      this.keyringName = keyringName
      this.sfConnection = new jsforce.Connection({
         loginUrl: salesforceConfig.loginUrl
      })
   }

   async connect(username, password, securityToken) {
      await this.sfConnection.login(username, password + securityToken)
      console.log('Connected to Salesforce')
   }

   async createLeadWithEncryptedData(leadData) {
      const ring = await this.grizzlyClient.ring.get(this.keyringName)

      // Encrypt sensitive fields
      const encryptedSSN = leadData.ssn ? await ring.encrypt(leadData.ssn, {
         asset: {
            id: `lead-${leadData.email}`,
            name: `${leadData.firstName} ${leadData.lastName} - SSN`,
            type: 'pii',
            field: 'ssn',
            crm: 'salesforce'
         }
      }) : null

      const encryptedIncome = leadData.annualIncome ? await ring.encrypt(String(leadData.annualIncome), {
         asset: {
            id: `lead-${leadData.email}`,
            name: `${leadData.firstName} ${leadData.lastName} - Income`,
            type: 'financial',
            field: 'annualIncome',
            crm: 'salesforce'
         }
      }) : null

      // Create Salesforce Lead
      const lead = await this.sfConnection.sobject('Lead').create({
         FirstName: leadData.firstName,
         LastName: leadData.lastName,
         Email: leadData.email,
         Company: leadData.company,
         Phone: leadData.phone,
         // Custom encrypted fields
         Encrypted_SSN__c: encryptedSSN,
         Encrypted_Income__c: encryptedIncome,
         Grizzly_Encrypted__c: true,
         Grizzly_KeyRing__c: this.keyringName
      })

      console.log(`Lead created: ${lead.id}`)

      return lead
   }

   async getLeadWithDecryptedData(leadId) {
      // Retrieve from Salesforce
      const lead = await this.sfConnection.sobject('Lead').retrieve(leadId)

      // Decrypt sensitive fields
      const decryptedData = {
         id: lead.Id,
         firstName: lead.FirstName,
         lastName: lead.LastName,
         email: lead.Email,
         company: lead.Company,
         phone: lead.Phone,
         ssn: null,
         annualIncome: null
      }

      if (lead.Encrypted_SSN__c) {
         decryptedData.ssn = await this.grizzlyClient.decrypt(lead.Encrypted_SSN__c)
      }

      if (lead.Encrypted_Income__c) {
         const income = await this.grizzlyClient.decrypt(lead.Encrypted_Income__c)
         decryptedData.annualIncome = parseFloat(income)
      }

      return decryptedData
   }

   async updateLeadEncryptedFields(leadId, updates) {
      const ring = await this.grizzlyClient.ring.get(this.keyringName)

      const sfUpdates = {}

      if (updates.ssn !== undefined) {
         sfUpdates.Encrypted_SSN__c = updates.ssn ? await ring.encrypt(updates.ssn, {
            asset: {
               id: `lead-${leadId}`,
               name: `Lead ${leadId} - SSN (Updated)`,
               type: 'pii',
               field: 'ssn',
               crm: 'salesforce'
            }
         }) : null
      }

      if (updates.annualIncome !== undefined) {
         sfUpdates.Encrypted_Income__c = updates.annualIncome ? await ring.encrypt(String(updates.annualIncome), {
            asset: {
               id: `lead-${leadId}`,
               name: `Lead ${leadId} - Income (Updated)`,
               type: 'financial',
               field: 'annualIncome',
               crm: 'salesforce'
            }
         }) : null
      }

      await this.sfConnection.sobject('Lead').update({
         Id: leadId,
         ...sfUpdates
      })

      console.log(`Lead updated: ${leadId}`)
   }
}

// Usage
const salesforce = new SecureSalesforceIntegration(grizzlyClient, {
   loginUrl: 'https://login.salesforce.com'
})

await salesforce.connect(
   process.env.SF_USERNAME,
   process.env.SF_PASSWORD,
   process.env.SF_SECURITY_TOKEN
)

// Create lead with encrypted sensitive data
const lead = await salesforce.createLeadWithEncryptedData({
   firstName: 'Sarah',
   lastName: 'Williams',
   email: 'sarah.williams@example.com',
   company: 'Acme Corp',
   phone: '555-0123',
   ssn: '987-65-4321',
   annualIncome: 150000
})

// Retrieve and decrypt
const decryptedLead = await salesforce.getLeadWithDecryptedData(lead.id)
console.log(decryptedLead.annualIncome)  // 150000

API Gateway Pattern

Encrypt/Decrypt at API Boundary

Create an API gateway that automatically encrypts outbound data and decrypts inbound data.

import express from 'express'
import { KeyClient } from '@grizzlycbg/grizzly-sdk'

class EncryptionGateway {
   constructor(grizzlyClient, keyringName = 'Gateway') {
      this.client = grizzlyClient
      this.keyringName = keyringName
   }

   // Middleware: Encrypt response data
   encryptResponseMiddleware(fieldsToEncrypt = []) {
      return async (req, res, next) => {
         const originalJson = res.json.bind(res)

         res.json = async (data) => {
            const ring = await this.client.ring.get(this.keyringName)
            const encrypted = { ...data }

            // Encrypt specified fields
            for (const field of fieldsToEncrypt) {
               if (encrypted[field]) {
                  encrypted[field] = await ring.encrypt(
                     typeof encrypted[field] === 'string'
                        ? encrypted[field]
                        : JSON.stringify(encrypted[field]),
                     {
                        asset: {
                           id: `api-response-${Date.now()}`,
                           name: `API Response - ${field}`,
                           type: 'api-response',
                           field,
                           endpoint: req.path
                        }
                     }
                  )
                  encrypted[`${field}_encrypted`] = true
               }
            }

            return originalJson(encrypted)
         }

         next()
      }
   }

   // Middleware: Decrypt request data
   decryptRequestMiddleware(fieldsToDecrypt = []) {
      return async (req, res, next) => {
         try {
            for (const field of fieldsToDecrypt) {
               if (req.body[field] && req.body[`${field}_encrypted`]) {
                  const decrypted = await this.client.decrypt(req.body[field])

                  try {
                     req.body[field] = JSON.parse(decrypted)
                  } catch {
                     req.body[field] = decrypted
                  }

                  delete req.body[`${field}_encrypted`]
               }
            }
            next()
         } catch (error) {
            res.status(400).json({ error: 'Decryption failed' })
         }
      }
   }
}

// Usage
const app = express()
app.use(express.json())

const gateway = new EncryptionGateway(grizzlyClient)

// Encrypt sensitive fields in responses
app.get(
   '/api/users/:id',
   gateway.encryptResponseMiddleware(['ssn', 'creditCard', 'medicalHistory']),
   async (req, res) => {
      const user = await db.users.findById(req.params.id)

      res.json({
         id: user.id,
         name: user.name,
         email: user.email,
         ssn: user.ssn,                    // Will be encrypted
         creditCard: user.creditCard,      // Will be encrypted
         medicalHistory: user.medicalHistory  // Will be encrypted
      })
   }
)

// Decrypt sensitive fields in requests
app.post(
   '/api/users',
   gateway.decryptRequestMiddleware(['ssn', 'creditCard']),
   async (req, res) => {
      // req.body.ssn and req.body.creditCard are now decrypted

      const user = await db.users.create(req.body)

      res.json({ id: user.id, created: true })
   }
)

app.listen(3000)

Data Sharing Compliance

GDPR-Compliant Data Export

Implement data export functionality that complies with GDPR requirements.

class GDPRDataExporter {
   constructor(grizzlyClient, keyringName) {
      this.client = grizzlyClient
      this.keyringName = keyringName
   }

   async exportUserData(userId, options = {}) {
      const ring = await this.client.ring.get(this.keyringName)

      // Gather all user data
      const userData = await this.collectUserData(userId)

      // Decrypt all encrypted fields for export
      const decryptedData = await this.decryptAllFields(userData)

      // Encrypt export package
      const exportPackage = {
         userId,
         exportDate: new Date().toISOString(),
         data: decryptedData,
         dataTypes: Object.keys(decryptedData)
      }

      const encrypted = await ring.encrypt(JSON.stringify(exportPackage), {
         asset: {
            id: `gdpr-export-${userId}`,
            name: `GDPR Export - User ${userId}`,
            type: 'gdpr-export',
            userId,
            exportDate: exportPackage.exportDate,
            compliance: 'gdpr'
         }
      })

      // Generate secure download token
      const downloadToken = crypto.randomBytes(32).toString('hex')

      // Store encrypted export with expiration
      await this.storeExport(userId, encrypted, downloadToken, {
         expiresIn: options.expiresIn || '7d'
      })

      return {
         userId,
         downloadToken,
         expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
      }
   }

   async collectUserData(userId) {
      // Collect from all sources
      const [profile, orders, payments, activity] = await Promise.all([
         db.users.findById(userId),
         db.orders.find({ userId }),
         db.payments.find({ userId }),
         db.activity.find({ userId })
      ])

      return {
         profile,
         orders,
         payments,
         activity
      }
   }

   async decryptAllFields(data) {
      // Recursively decrypt all encrypted fields
      const decrypted = {}

      for (const [key, value] of Object.entries(data)) {
         if (Array.isArray(value)) {
            decrypted[key] = await Promise.all(
               value.map(item => this.decryptAllFields(item))
            )
         } else if (typeof value === 'object' && value !== null) {
            decrypted[key] = await this.decryptAllFields(value)
         } else if (typeof value === 'string' && this.isEncrypted(value)) {
            try {
               decrypted[key] = await this.client.decrypt(value)
            } catch {
               decrypted[key] = value  // Keep as-is if not Grizzly-encrypted
            }
         } else {
            decrypted[key] = value
         }
      }

      return decrypted
   }

   isEncrypted(value) {
      // Check if string looks like Grizzly-encrypted data
      // (This is a simplified check)
      return typeof value === 'string' && value.length > 100
   }

   async storeExport(userId, encryptedData, token, options) {
      await db.exports.insert({
         userId,
         token,
         data: encryptedData,
         createdAt: new Date(),
         expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
      })
   }
}

// Usage
const exporter = new GDPRDataExporter(grizzlyClient, 'GDPR')

// User requests data export
const exportInfo = await exporter.exportUserData('user-123')

console.log('Data export ready:', exportInfo.downloadToken)
// Send download link to user: /download/gdpr/${exportInfo.downloadToken}

Next Steps