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!,
});
// Raw REST calls use: Authorization: `Bearer ${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 },
});
// ...gather tags, segments, events → return one bundle
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:
// A user asks for all their data — export the whole bundle to JSON.
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.
1---2name: intercom-data-handling3description: Implement Intercom data handling for GDPR, contact export, data retention, and PII. Use when handling sensitive Intercom contact data, fulfilling a data subject access or deletion request, redacting PII in logs, or setting retention policy for cached Intercom records. Trigger with phrases like "intercom data", "intercom PII", "intercom GDPR", "intercom data retention", "intercom privacy", "intercom CCPA", "intercom data export", "intercom delete contact".4license: MIT5---6# Intercom Data Handling
7
8## Overview
9
10Handle sensitive contact data in Intercom integrations with GDPR/CCPA compliance:
11data export via the Data Export API, contact deletion with an audit trail, PII
12redaction in logs, and data retention policies. This skill gives you a lean map of
13the five workflows here; the full copy-ready TypeScript lives in
14[references/implementation.md](references/implementation.md) and worked usage in
15[references/examples.md](references/examples.md).
16
17## Prerequisites
18
19- Understanding of GDPR/CCPA requirements
20- `intercom-client` SDK installed
21- Database for audit logging
22- Familiarity with Intercom's contact and conversation data model
23
24## Authentication
25
26Every call authenticates with an Intercom access token via a Bearer header. Store
27it as `INTERCOM_ACCESS_TOKEN` in the environment — never hardcode it and never log
28it:
29
30```typescript
31import { IntercomClient } from "intercom-client";
32
33const client = new IntercomClient({
34 token: process.env.INTERCOM_ACCESS_TOKEN!,
35});
36// Raw REST calls use: Authorization: `Bearer ${process.env.INTERCOM_ACCESS_TOKEN}`
37```
38
39Grant the token the minimum scopes needed (read contacts/conversations for export,
40write/delete for erasure). Rotate it if it ever appears in a log or a diff.
41
42## Data Classification for Intercom
43
44| Category | Intercom Fields | Handling |
45|----------|----------------|----------|
46| PII | `email`, `name`, `phone`, `location` | Encrypt at rest, redact in logs |
47| Identifiers | `id`, `external_id`, `user_id` | Use for lookups, no display |
48| Conversation content | `body`, `conversation_parts` | May contain PII, scan before logging |
49| Custom attributes | User-defined | Depends on content |
50| System metadata | `created_at`, `updated_at`, `role` | Standard handling |
51
52## Instructions
53
54The five workflows below compose into a compliant Intercom data lifecycle. Follow
55the summary here, then open [references/implementation.md](references/implementation.md)
56for the complete function bodies.
57
581. **DSAR export** — `exportContactData(contactId)` gathers the contact profile,
59 all conversations (with parts), tags, segments, and data events into one bundle.
60 This is the "give me all my data" request.
612. **Right to deletion (Article 17)** — `deleteContactData(contactId)` exports for
62 the audit trail *first*, then deletes from Intercom and every local cache, and
63 records a PII-free audit entry (email is hashed, not stored).
643. **Bulk data export** — `bulkExportMessages(start, end)` kicks off the async
65 `/export/messages/data` job; `checkExportStatus(jobId)` polls until a CSV
66 `download_url` is returned.
674. **PII redaction in logs** — `redactIntercomData(data)` masks a fixed
68 `PII_FIELDS` set (including nested `custom_attributes.*`) before anything is
69 logged.
705. **Retention enforcement** — `enforceRetention()` sweeps cached records past
71 their `RETENTION` window on a daily cron, and never touches the 7-year audit log.
72
73Data minimization underpins all five: sync only the fields you need so the erasure
74and breach surface stays small (see [references/examples.md](references/examples.md)).
75
76Here is the entry-point skeleton — the export that DSAR and deletion both build on:
77
78```typescript
79const contact = await client.contacts.find({ contactId });
80const convList = await client.conversations.search({
81 query: { field: "contact_ids", operator: "=", value: contactId },
82});
83// ...gather tags, segments, events → return one bundle
84```
85
86## Output
87
88Each workflow returns a structured, PII-aware result:
89
90- **DSAR export** → an object with `contact`, `conversations[]`, `tags[]`,
91 `segments[]`, and `events[]` — the full data bundle to hand to the requester.
92- **Deletion** → `{ deleted: true, auditRecord }` where `auditRecord` holds the
93 action, hashed email, timestamp, purged data sources, and conversation count —
94 proof of erasure that contains no raw PII.
95- **Bulk export** → a `job_identifier`, then a `{ status, downloadUrl }` once the
96 CSV is ready.
97- **Redaction** → the same object shape with PII fields replaced by `[REDACTED]`.
98- **Retention** → `{ deleted: { [cacheType]: count } }` per swept cache type.
99
100## Error Handling
101
102| Issue | Cause | Solution |
103|-------|-------|----------|
104| Export job stuck in "pending" | Large dataset | Poll every 30s, timeout at 1h |
105| Deletion returns 404 | Already deleted | Log and continue (idempotent) |
106| PII in conversation bodies | User-submitted content | Scan with regex, redact in logs |
107| Audit log gap | Failed write | Use write-ahead log or queue |
108
109## Examples
110
111Full worked examples — fulfilling a DSAR, honoring a deletion request, polling a
112bulk export to completion, and redacting before logging — are in
113[references/examples.md](references/examples.md). The shortest one:
114
115```typescript
116// A user asks for all their data — export the whole bundle to JSON.
117const bundle = await exportContactData("5f3c9b2e8a1d4e0012ab34cd");
118await fs.writeFile(`dsar/${bundle.contact.id}.json`, JSON.stringify(bundle, null, 2));
119```
120
121## Resources
122
123- [Full implementation walkthrough](references/implementation.md) — all five workflows, copy-ready
124- [Worked examples](references/examples.md) — end-to-end usage + data minimization
125- [Data Export API](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/data-export/data_export)
126- [Contacts API](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/contacts)
127- [Intercom Privacy](https://www.intercom.com/privacy)
128
129## Next Steps
130
131For enterprise access control and permission scoping on top of these data
132workflows, see the `intercom-enterprise-rbac` skill in this pack.