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
- File Encryption - Encrypt files and documents
- Database Encryption - Encrypt database fields
- Security Best Practices - Production security guidelines
- SDK Examples - More SDK usage patterns