Customer.io Security Basics
Overview
Implement security best practices for Customer.io: secrets management for API credentials, PII sanitization before sending data, webhook signature verification (HMAC-SHA256), API key rotation, and GDPR/CCPA data deletion compliance.
Prerequisites
- Customer.io account with admin access
- Understanding of your data classification (what is PII)
- Secrets management system (recommended for production)
Instructions
Step 1: Secure Credential Storage
const siteId = process.env.CUSTOMERIO_SITE_ID;
const trackKey = process.env.CUSTOMERIO_TRACK_API_KEY;
import { SecretManagerServiceClient } from "@google-cloud/secret-manager";
const secretClient = new SecretManagerServiceClient();
async function getSecret(name: string): Promise<string> {
const [version] = await secretClient.accessSecretVersion({
name: `projects/my-project/secrets/${name}/versions/latest`,
});
return version.payload?.data?.toString() ?? "";
}
async function createCioClient() {
const [siteId, trackKey] = await Promise.all([
getSecret("customerio-site-id"),
getSecret("customerio-track-api-key"),
]);
return new TrackClient(siteId, trackKey, { region: RegionUS });
}
Step 2: PII Sanitization
const NEVER_SEND = new Set([
"ssn", "social_security", "tax_id",
"credit_card", "card_number", "cvv",
"password", "password_hash",
"bank_account", "routing_number",
]);
const HASH_FIELDS = new Set([
"phone", "phone_number",
"ip_address", "ip",
"address", "street_address",
]);
import { createHash } from "crypto";
function hashValue(value: string): string {
return createHash("sha256").update(value).digest("hex").substring(0, 16);
}
export function sanitizeAttributes(
attrs: Record<string, any>
): Record<, > {
: <, > = {};
( [key, value] .(attrs)) {
lowerKey = key.();
(.(lowerKey)) ;
(.(lowerKey) && value === ) {
clean[] = (value);
;
}
clean[key] = value;
}
clean;
}
{ , } ;
cio = (siteId, trackKey, { : });
cio.(, ({
: ,
: ,
: ,
: ,
: ,
}));
Step 3: Webhook Signature Verification
Customer.io signs webhook payloads with HMAC-SHA256. Always verify before processing.
import { createHmac, timingSafeEqual } from "crypto";
import { Request, Response, NextFunction } from "express";
const WEBHOOK_SECRET = process.env.CUSTOMERIO_WEBHOOK_SECRET!;
export function verifyCioWebhook(
req: Request,
res: Response,
next: NextFunction
): void {
const signature = req.headers["x-cio-signature"] as string;
if (!signature) {
res.status(401).json({ error: "Missing signature header" });
return;
}
const rawBody = (req as any).rawBody as Buffer;
if (!rawBody) {
res.status(500).json({ error: "Raw body not available" });
return;
}
expected = (, )
.(rawBody)
.();
valid = (
.(signature),
.(expected)
);
(!valid) {
res.().({ : });
;
}
();
}
express ;
app = ();
app.(, express.({ : }));
app.(, verifyCioWebhook, {
event = .((req )..());
res.();
});
Step 4: API Key Rotation
async function rotateKeys() {
console.log("Customer.io Key Rotation Procedure:");
console.log("1. Go to Settings > Workspace Settings > API & Webhook Credentials");
console.log("2. Click 'Regenerate' next to the key you want to rotate");
console.log("3. Copy the NEW key");
console.log("4. Update your secrets manager:");
console.log(" - GCP: gcloud secrets versions add customerio-track-api-key --data-file=-");
console.log(" - AWS: aws ssm put-parameter --name /cio/track-api-key --value NEW_KEY --overwrite");
console.log("5. Deploy your application (or restart to pick up new secrets)");
console.log("6. Verify: run the connectivity test script");
console.log("7. The old key is immediately invalidated upon regeneration");
console.log("");
console.log("IMPORTANT: Regenerating a key IMMEDIATELY invalidates the old key.");
.();
}
Step 5: GDPR/CCPA Data Deletion
import { TrackClient, RegionUS } from "customerio-node";
const cio = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_TRACK_API_KEY!,
{ region: RegionUS }
);
async function handleDeletionRequest(userId: string): Promise<void> {
await cio.suppress(userId);
console.log(`User ${userId} suppressed (no more messages)`);
await cio.destroy(userId);
console.log(`User ${userId} deleted from Customer.io`);
console.log(`GDPR deletion completed for ${userId} at ${new Date().toISOString()}`);
}
(): <> {
( userId userIds) {
{
(userId);
} (: ) {
.();
}
( (r, ));
}
}
Security Checklist
Error Handling
| Issue | Solution |
|---|
| Credentials exposed in git | Rotate immediately, scan git history with trufflehog |
| PII accidentally sent | Delete user with destroy(), update sanitization rules |
| Webhook signature mismatch | Verify webhook secret matches Customer.io dashboard |
| Key rotation causes downtime | Update secrets manager BEFORE regenerating in dashboard |
Resources
Next Steps
After implementing security, proceed to customerio-prod-checklist for production readiness.