customerio-security-basics
Implement security best practices for Customer.io including credential management and PII handling.
Install
mkdir -p .claude/skills/customerio-security-basics && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/4706" && unzip -o skill.zip -d .claude/skills/customerio-security-basics && rm skill.zipInstalls to .claude/skills/customerio-security-basics
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Apply Customer.io security best practices.Key capabilities
- →Store API credentials securely using environment variables or secret managers
- →Sanitize PII from user data before sending to Customer.io
- →Verify webhook signatures using HMAC-SHA256
- →Implement API key rotation procedures
- →Handle GDPR/CCPA data deletion requests
- →Generate a security checklist for Customer.io integrations
How it works
The skill provides code examples and instructions for securing Customer.io integrations by managing credentials, sanitizing PII, verifying webhooks, and handling data deletion.
Inputs & outputs
When to use customerio-security-basics
- →Secure API credentials with secret management
- →Validate incoming webhook signatures
- →Implement PII sanitization for user events
- →Audit compliance for GDPR/CCPA requirements
About this skill
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
// lib/customerio-secrets.ts
// NEVER hardcode credentials — use environment variables or a secrets manager
// Option A: Environment variables (acceptable for most apps)
const siteId = process.env.CUSTOMERIO_SITE_ID;
const trackKey = process.env.CUSTOMERIO_TRACK_API_KEY;
// Option B: GCP Secret Manager (recommended for production)
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
// lib/customerio-sanitize.ts
// Sanitize user data BEFORE sending to Customer.io
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<string, any> {
const clean: Record<string, any> = {};
for (const [key, value] of Object.entries(attrs)) {
const lowerKey = key.toLowerCase();
// Strip highly sensitive fields entirely
if (NEVER_SEND.has(lowerKey)) continue;
// Hash PII fields
if (HASH_FIELDS.has(lowerKey) && typeof value === "string") {
clean[`${key}_hash`] = hashValue(value);
continue;
}
clean[key] = value;
}
return clean;
}
// Usage
import { TrackClient, RegionUS } from "customerio-node";
const cio = new TrackClient(siteId, trackKey, { region: RegionUS });
await cio.identify("user-123", sanitizeAttributes({
email: "[email protected]", // Kept (needed for email delivery)
first_name: "Jane", // Kept
phone: "+1-555-0123", // Hashed → phone_hash
ssn: "123-45-6789", // STRIPPED entirely
plan: "pro", // Kept
}));
Step 3: Webhook Signature Verification
Customer.io signs webhook payloads with HMAC-SHA256. Always verify before processing.
// middleware/customerio-webhook.ts
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;
}
// req.body must be the raw buffer — configure Express accordingly
const rawBody = (req as any).rawBody as Buffer;
if (!rawBody) {
res.status(500).json({ error: "Raw body not available" });
return;
}
const expected = createHmac("sha256", WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
const valid = timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
if (!valid) {
res.status(401).json({ error: "Invalid signature" });
return;
}
next();
}
// Express setup — raw body required for signature verification
import express from "express";
const app = express();
app.use("/webhooks/customerio", express.raw({ type: "application/json" }));
app.post("/webhooks/customerio", verifyCioWebhook, (req, res) => {
const event = JSON.parse((req as any).rawBody.toString());
// Process verified webhook event
res.sendStatus(200);
});
Step 4: API Key Rotation
// scripts/rotate-cio-keys.ts
// Rotation procedure — zero downtime
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.");
console.log("Update secrets BEFORE regenerating, or plan for brief downtime.");
}
Step 5: GDPR/CCPA Data Deletion
// services/customerio-gdpr.ts
import { TrackClient, RegionUS } from "customerio-node";
const cio = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_TRACK_API_KEY!,
{ region: RegionUS }
);
// GDPR Right to Erasure / CCPA Delete My Data
async function handleDeletionRequest(userId: string): Promise<void> {
// 1. Suppress — stop all messaging immediately
await cio.suppress(userId);
console.log(`User ${userId} suppressed (no more messages)`);
// 2. Destroy — remove profile and all data from Customer.io
await cio.destroy(userId);
console.log(`User ${userId} deleted from Customer.io`);
// 3. Log the deletion for compliance audit trail
console.log(`GDPR deletion completed for ${userId} at ${new Date().toISOString()}`);
}
// Bulk deletion (e.g., processing deletion requests from a queue)
async function bulkDelete(userIds: string[]): Promise<void> {
for (const userId of userIds) {
try {
await handleDeletionRequest(userId);
} catch (err: any) {
// Log but continue — don't let one failure block others
console.error(`Deletion failed for ${userId}: ${err.message}`);
}
// Respect rate limits
await new Promise((r) => setTimeout(r, 100));
}
}
Security Checklist
- API keys stored in secrets manager (not
.envin production) - API key rotation schedule set (every 90 days)
- Webhook signatures verified (HMAC-SHA256 with
timingSafeEqual) - PII sanitized before sending to Customer.io
- Highly sensitive data (SSN, credit card) never sent
- GDPR deletion endpoint implemented (
suppress+destroy) -
.envfiles in.gitignore - Audit log for deletion requests
- Minimum necessary data principle applied
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.
When not to use it
- →When Customer.io is not part of the system architecture
- →When data classification is not understood
Prerequisites
Limitations
- →Requires a Customer.io account
- →Assumes knowledge of data classification
- →Relies on external secrets management systems for production
How it compares
This skill offers a structured approach to implementing Customer.io security best practices, unlike ad-hoc security measures or manual data handling.
Compared to similar skills
customerio-security-basics side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| customerio-security-basics (this skill) | 1 | 27d | Review | Intermediate |
| auth-patterns | 7 | 7mo | Review | Intermediate |
| clerk-incident-runbook | 1 | 27d | Caution | Intermediate |
| gamma-enterprise-rbac | 1 | 27d | Review | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by jeremylongshore
View all by jeremylongshore →You might also like
auth-patterns
davepoon
This skill should be used when the user asks about "authentication in Next.js", "NextAuth", "Auth.js", "middleware auth", "protected routes", "session management", "JWT", "login flow", or needs guidance on implementing authentication and authorization in Next.js applications.
clerk-incident-runbook
jeremylongshore
Incident response procedures for Clerk authentication issues. Use when handling auth outages, security incidents, or production authentication problems. Trigger with phrases like "clerk incident", "clerk outage", "clerk down", "auth not working", "clerk emergency".
gamma-enterprise-rbac
jeremylongshore
Implement enterprise role-based access control for Gamma integrations. Use when configuring team permissions, multi-tenant access, or enterprise authorization patterns. Trigger with phrases like "gamma RBAC", "gamma permissions", "gamma access control", "gamma enterprise", "gamma roles".
jwt-auth
dadbodgeoff
Implement secure JWT authentication with refresh token rotation, secure storage, and automatic renewal. Use when building authentication for SPAs, mobile apps, or APIs that need stateless auth with refresh capabilities.
maintainx-security-basics
jeremylongshore
Configure MaintainX API security, credential management, and access control. Use when securing API keys, implementing access controls, or hardening your MaintainX integration. Trigger with phrases like "maintainx security", "maintainx api key security", "secure maintainx", "maintainx credentials", "maintainx access control".
clerk-data-handling
jeremylongshore
Handle user data, privacy, and GDPR compliance with Clerk. Use when implementing data export, user deletion, or privacy compliance features. Trigger with phrases like "clerk user data", "clerk GDPR", "clerk privacy", "clerk data export", "clerk delete user".