Intercom Data Handling
Overview
Handle sensitive contact data in Intercom integrations with GDPR/CCPA compliance:
data export via the Data Export API, contact deletion with an audit trail, PII
redaction in logs, and data retention policies. This skill gives you a lean map of
the five workflows here; the full copy-ready TypeScript lives in
references/implementation.md and worked usage in
references/examples.md.
Prerequisites
- Understanding of GDPR/CCPA requirements
intercom-client SDK installed
- Database for audit logging
- Familiarity with Intercom's contact and conversation data model
Authentication
Every call authenticates with an Intercom access token via a Bearer header. Store
it as INTERCOM_ACCESS_TOKEN in the environment — never hardcode it and never log
it:
import { IntercomClient } from "intercom-client";
const client = new IntercomClient({
token: process.env.INTERCOM_ACCESS_TOKEN!,
});
Grant the token the minimum scopes needed (read contacts/conversations for export,
write/delete for erasure). Rotate it if it ever appears in a log or a diff.
Data Classification for Intercom
| Category | Intercom Fields | Handling |
|---|
| PII | email, name, phone, location | Encrypt at rest, redact in logs |
| Identifiers | id, external_id, user_id | Use for lookups, no display |
| Conversation content | body, conversation_parts | May contain PII, scan before logging |
| Custom attributes | User-defined | Depends on content |
| System metadata | created_at, updated_at, role | Standard handling |
Instructions
The five workflows below compose into a compliant Intercom data lifecycle. Follow
the summary here, then open references/implementation.md
for the complete function bodies.
- DSAR export —
exportContactData(contactId) gathers the contact profile,
all conversations (with parts), tags, segments, and data events into one bundle.
This is the "give me all my data" request.
- Right to deletion (Article 17) —
deleteContactData(contactId) exports for
the audit trail first, then deletes from Intercom and every local cache, and
records a PII-free audit entry (email is hashed, not stored).
- Bulk data export —
bulkExportMessages(start, end) kicks off the async
/export/messages/data job; checkExportStatus(jobId) polls until a CSV
download_url is returned.
- PII redaction in logs —
redactIntercomData(data) masks a fixed
PII_FIELDS set (including nested custom_attributes.*) before anything is
logged.
- Retention enforcement —
enforceRetention() sweeps cached records past
their RETENTION window on a daily cron, and never touches the 7-year audit log.
Data minimization underpins all five: sync only the fields you need so the erasure
and breach surface stays small (see references/examples.md).
Here is the entry-point skeleton — the export that DSAR and deletion both build on:
const contact = await client.contacts.find({ contactId });
const convList = await client.conversations.search({
query: { field: "contact_ids", operator: "=", value: contactId },
});
Output
Each workflow returns a structured, PII-aware result:
- DSAR export → an object with
contact, conversations[], tags[],
segments[], and events[] — the full data bundle to hand to the requester.
- Deletion →
{ deleted: true, auditRecord } where auditRecord holds the
action, hashed email, timestamp, purged data sources, and conversation count —
proof of erasure that contains no raw PII.
- Bulk export → a
job_identifier, then a { status, downloadUrl } once the
CSV is ready.
- Redaction → the same object shape with PII fields replaced by
[REDACTED].
- Retention →
{ deleted: { [cacheType]: count } } per swept cache type.
Error Handling
| Issue | Cause | Solution |
|---|
| Export job stuck in "pending" | Large dataset | Poll every 30s, timeout at 1h |
| Deletion returns 404 | Already deleted | Log and continue (idempotent) |
| PII in conversation bodies | User-submitted content | Scan with regex, redact in logs |
| Audit log gap | Failed write | Use write-ahead log or queue |
Examples
Full worked examples — fulfilling a DSAR, honoring a deletion request, polling a
bulk export to completion, and redacting before logging — are in
references/examples.md. The shortest one:
const bundle = await exportContactData("5f3c9b2e8a1d4e0012ab34cd");
await fs.writeFile(`dsar/${bundle.contact.id}.json`, JSON.stringify(bundle, null, 2));
Resources
Next Steps
For enterprise access control and permission scoping on top of these data
workflows, see the intercom-enterprise-rbac skill in this pack.