Customer Support Privacy

PII Detection in Support Tickets

Customers volunteer everything to support: card numbers, ID scans, medical details, passwords. This guide shows how to detect and mask that data in Zendesk, Intercom, and Freshdesk — with webhook-based scanning code, agent-visible masking that keeps tickets workable, and analytics that stay useful after scrubbing.

Explore the Guide

Why Support Tickets Fill Up With PII You Never Asked For

Support is the one channel where customers are actively trying to hand you sensitive data. A user who wants a refund pastes the full card number "to make it faster". A patient disputing an invoice describes their diagnosis. Someone locked out of their account sends a photo of their passport — unprompted — because it worked with their bank. Unlike a checkout form, which collects exactly the fields it is designed to collect, a ticket is a free-text box connected directly to a customer's sense of urgency. Over-sharing is the norm, not the exception.

The result is that helpdesks quietly become one of the largest unmanaged PII stores in the company. Ticket bodies, comment threads, chat transcripts, satisfaction-survey verbatims, and attachments accumulate for years, indexed and searchable by every agent, team lead, and — via analytics exports and BI connectors — by teams far outside support. Agents also copy ticket content outward: into Slack threads when they escalate, into Jira when they file bugs, into macros and knowledge-base drafts. Every copy inherits the pasted card number.

The exposure is broader than most audits assume. Helpdesk data routinely flows to sub-processors (translation services, AI summarizers, QA scoring tools), to offshore BPO partners with wide read access, and into training corpora for support chatbots. A single ticket containing a name, address, and payment card can therefore surface in half a dozen systems, none of which appear in the data map as "stores cardholder data". PCI DSS assessors increasingly ask about helpdesks for exactly this reason, and GDPR's data-minimization principle applies to a Zendesk instance just as it does to a database.

The fix is not telling customers to stop over-sharing — they won't — but detecting sensitive data the moment it arrives and masking it before it spreads. Because the PII Detection API returns typed entities with offsets, confidence scores, and a ready-masked version of the text, the entire workflow — scan on ticket creation, redact in place, tag for audit — fits in a small webhook handler, which this guide builds step by step. You can preview detection behavior on a sample ticket right now in the live demo.

What to Detect in Tickets — and What to Leave Alone

Ticket scanning has a property most channels lack: some PII is supposed to be there. The requester's name and email are the ticket's addressing metadata; masking them everywhere would break support itself. Good policy distinguishes three classes. Never-store data — full payment card numbers, CVVs, passwords, government ID numbers, one-time codes — should be masked unconditionally, at full strength, the moment they are detected. There is no support workflow that legitimately needs a CVV in a ticket body.

Workflow data — the requester's own name and contact details in the ticket header — should generally be left intact in the working view but excluded from exports and analytics. This is where the API's scoping parameters earn their keep: pass entities to target only the high-risk types on ingest, and use custom_instruction — for example, "do not flag the requester name Maria Novak or the support address [email protected]" — to stop the ticket's own addressing data from generating noise.

Contextual data — third-party names, addresses, dates of birth, health details, order and account identifiers — sits in between: often necessary to resolve the case, rarely necessary to keep after resolution. The pragmatic policy is time-based: leave contextual entities visible while the ticket is open, then run a scrubbing pass at closure (or after a fixed grace period) that masks them before long-term retention. Detection confidence supports this nuance too: high-confidence card numbers get auto-redacted; medium-confidence hits can simply add a warning tag an agent can review.

Entity scope that works for most helpdesks: unconditional — CREDIT_CARD_NUMBER, CVV_NUMBER, SSN, NATIONAL_ID, PASSPORT_NUMBER, PASSWORD, AUTH_TOKEN, IBAN_CODE; at-closure — PERSON_NAME, ADDRESS, PHONE_NUMBER, DATE_OF_BIRTH, MEDICAL_DATA. The full catalog of 150+ types is on the entities page.

Integration Patterns for Zendesk, Intercom & Freshdesk

All three major helpdesks expose the same three hooks — an outbound event (webhook), a REST API to write redacted content back, and native redaction endpoints for permanent removal. The pattern is identical; only the endpoint names change.

Zendesk

Register a webhook fired by a trigger on "ticket created / comment added" that posts the comment body to your scanner. On detection, call the Ticket Comment Redaction API (or comment_redactions for permanent removal from history and attachments-in-comments), then tag the ticket (e.g. pii_redacted) so views and SLAs can track it. For agent-composed replies, the same trigger on outbound comments catches PII agents paste back to customers.

Intercom

Subscribe to conversation.user.created and conversation.user.replied webhook topics. Scan each conversation part as it arrives; because Intercom is chat-first, latency matters — scan asynchronously and use the Conversations API to edit or note the offending part, and conversation attributes to record which entity types appeared. The same scanner protects Fin/bot training exports, closing the loop with your chatbot PII filtering.

Freshdesk

Use automation rules ("ticket created" / "note added") to fire a webhook with the ticket body, scan, then PUT the masked description back via the Tickets API and add a private note summarizing what was masked. Freshdesk's marketplace app framework can also render an in-agent widget showing detected entities with an unmask-on-demand control backed by your access policy.

In-House & Other Desks

Running your own support stack, or Help Scout, Front, Salesforce Service Cloud? The pattern holds: intercept message-created events, scan, persist the masked body, store the raw only if a policy requires it and then encrypted with restricted access. In a custom stack you can go one step further and scan before first persistence, so the raw value never touches the database at all.

Helpdesk Integration Points at a Glance

Where to hook scanning, how to write the redaction back, and what each platform offers for permanent removal.

Capability Zendesk Intercom Freshdesk
Scan trigger Trigger + webhook on ticket/comment events Webhook topics (conversation created/replied) Automation rule firing a webhook
Write-back for masking Comment Redaction API; ticket field updates Conversations API (edit part, add note) Tickets API (PUT description), private notes
Permanent removal Yes — redaction endpoints strip content from history Contact/conversation deletion; part edit Yes — via ticket update; attachment delete API
Tagging for audit Ticket tags + custom fields Conversation attributes + tags Tags + custom ticket fields
Attachment access Attachment URLs on comments Attachment URLs on parts Attachment URLs on tickets/notes
Typical scan latency budget Seconds (async, before broad agent access) Sub-second preferred (live chat), async fallback Seconds (async on create/update)
Attachments are the blind spot. Photographed IDs, statements, and screenshots carry more sensitive data than ticket text, and none of the platforms scan them for you. Fetch each attachment from the webhook payload's URL, extract text (OCR for images and scanned PDFs), and run the same detection call. The full extraction workflow is in the document & PDF scanning guide.

Webhook-Based Ticket Scanning, End to End

Start with the raw detection call your webhook will make. A typical over-sharing ticket looks like this — note the mixture of legitimate workflow data and never-store data:

# Scan a new ticket body for high-risk entities
curl -X POST https://piidetectionapi.com/api/moderate.php \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "YOUR_API_KEY",
    "api_type": "pii_detection",
    "text": "Please refund my order. Card: 4111 1111 1111 1234, exp 04/27, CVV 812. Im Anna Schmidt, Hauptstrasse 12, Berlin. You can also reach me at +49 170 555 0117.",
    "entities": ["CREDIT_CARD_NUMBER", "CREDIT_CARD_EXPIRATION_DATE", "CVV_NUMBER",
                 "SSN", "NATIONAL_ID", "PASSPORT_NUMBER", "PASSWORD", "IBAN_CODE",
                 "PERSON_NAME", "ADDRESS", "PHONE_NUMBER"],
    "mask_mode": "replace",
    "threshold": 0.6,
    "custom_instruction": "Do not flag order numbers or ticket reference codes."
  }'
cURL — ticket body scan

The response identifies each entity with offsets and confidence, and anonymized_text arrives ready to write back. The Node.js handler below is a complete Zendesk-style webhook receiver: it scans the comment, redacts via the helpdesk API only when high-risk types appear, and tags the ticket for auditability. The same skeleton serves Intercom and Freshdesk with different write-back URLs:

// Express webhook: scan new comments, redact high-risk PII, tag ticket
const HIGH_RISK = ["CREDIT_CARD_NUMBER", "CVV_NUMBER", "SSN",
                   "NATIONAL_ID", "PASSPORT_NUMBER", "PASSWORD", "IBAN_CODE"];

app.post("/hooks/ticket-comment", async (req, res) => {
  res.sendStatus(200);                          // ack fast; work async
  const { ticket_id, comment_id, body } = req.body;

  const scan = await fetch("https://piidetectionapi.com/api/moderate.php", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      api_key: process.env.PII_API_KEY,
      api_type: "pii_detection",
      text: body,
      entities: [...HIGH_RISK, "PERSON_NAME", "ADDRESS", "PHONE_NUMBER"],
      mask_mode: "replace",
      threshold: 0.6
    })
  }).then(r => r.json());

  const risky = scan.detected_entities
    .filter(e => HIGH_RISK.includes(e.type) && e.confidence >= 0.8);
  if (!risky.length) return;

  // 1) Replace the comment with the masked rendering
  await zendesk.put(
    `/api/v2/tickets/${ticket_id}/comments/${comment_id}/redact`,
    { text: scan.anonymized_text });

  // 2) Tag + audit trail (types and counts only — never raw values)
  await zendesk.put(`/api/v2/tickets/${ticket_id}`, {
    ticket: { additional_tags: ["pii_redacted"],
              comment: { public: false,
                body: `Auto-redacted: ${risky.map(e => e.type).join(", ")}` } }
  });
});
JavaScript — Node.js webhook handler

Two operational details matter. Acknowledge the webhook immediately and do the scan-and-redact asynchronously — helpdesks retry slow webhooks and you do not want duplicate redactions. And make the handler idempotent: record scanned comment IDs so a redelivered event is a no-op rather than a second private note.

Agent-Visible Masking That Keeps Tickets Workable

Full redaction is right for card data, but masking everything an agent might need turns support into archaeology. The middle path is agent-visible masking: the working view shows typed placeholders — "customer at [ADDRESS] reports her card [CREDIT_CARD_NUMBER] was charged twice" — which preserve the story of the ticket while removing the values. Typed placeholders matter: an agent who sees [CREDIT_CARD_NUMBER] knows the customer already sent a card number and can respond appropriately ("we've removed your card details for your security") instead of asking the customer to repeat it.

Where a workflow genuinely needs a value — verifying the last four digits, calling the customer back — implement unmask-on-demand: the raw value (or better, only the fragment needed) is stored encrypted and fetched through an access-controlled endpoint that logs who unmasked what and why. In practice fewer than 2% of masked values ever get unmasked, which tells you how little of the raw data support work actually requires. The Python service below produces both renderings in one API call — masked body for the ticket, and a structured entity map for the vault:

import requests

def process_ticket_body(body):
    """Returns (masked_body, entity_map) for agent view + secure vault."""
    resp = requests.post(
        "https://piidetectionapi.com/api/moderate.php",
        json={
            "api_key": "YOUR_API_KEY",
            "api_type": "pii_detection",
            "text": body,
            "entities": ["CREDIT_CARD_NUMBER", "CVV_NUMBER", "SSN",
                         "PERSON_NAME", "ADDRESS", "PHONE_NUMBER",
                         "DATE_OF_BIRTH", "MEDICAL_DATA"],
            "mask_mode": "replace",
            "threshold": 0.6,
        },
        timeout=30,
    )
    data = resp.json()

    entity_map = []
    for i, e in enumerate(data["detected_entities"]):
        entity_map.append({
            "ref": f"ent_{i}",
            "type": e["type"],
            "start": e["start"],
            "end": e["end"],
            "confidence": e["confidence"],
            # store e["text"] ONLY in the encrypted vault, keyed by ref
        })

    return data["anonymized_text"], entity_map

masked, ents = process_ticket_body(
    "Charge on my card 4111 1111 1111 1234 - Anna Schmidt, DOB 03/03/1987")
print(masked)
# Charge on my card [CREDIT_CARD_NUMBER] - [NAME], DOB [DATE_OF_BIRTH]
Python — masked view + vault entity map

For deterministic cross-ticket correlation — "how many tickets did this customer open?" without exposing identity to analysts — run a second pass with mask_mode: "hash". The same email address always yields the same hash, so joins keep working on scrubbed data. This is the bridge to the analytics section below.

Analytics on Scrubbed Tickets: Keep the Signal, Drop the Identity

The historical objection to redacting tickets was "we need the data for analytics". It turns out almost nothing support analytics needs is the PII itself. Topic modeling, CSAT driver analysis, deflection measurement, agent QA, escalation prediction — all operate on what the ticket is about, not who the customer is. Masked text with typed placeholders preserves the aboutness completely: "[NAME] cannot log in after the [DATE] release" clusters exactly as well as the raw sentence, and arguably better, since names and numbers are noise to a topic model anyway.

A scrubbing pass therefore belongs at the boundary between the helpdesk and every downstream consumer: the nightly export to the warehouse, the BI connector, the training-set builder for your support LLM, the sample sets sent to QA vendors. Feed them anonymized_text plus the entity metadata, and three new analytics become possible that raw data never offered: PII inflow rate (which channels and forms cause customers to over-share — a web form with a free-text box next to a payment page will light up), agent hygiene (which teams paste sensitive data into escalations), and compliance posture (tickets containing card data per week, trending to zero as your prevention improves).

Where identity-level analysis is legitimately needed — churn-risk joins, lifetime-value segmentation — hash mode provides pseudonymous keys that join across tickets and even across systems without exposing the underlying identity to the analyst. Access to the mapping stays with the small team that operates the vault. This layered design typically satisfies both the analytics team and the DPO, a combination rarely achieved with raw exports; the regulatory reasoning is laid out in the GDPR PII detection guide.

LLM training warning: if support transcripts feed a fine-tuned model or a RAG index, unscrubbed PII can resurface verbatim in generated answers to other users. Scrub transcripts before they enter the corpus — the patterns are in PII detection for LLM guardrails and RAG pipeline PII protection.

Retention, Regulations, and the Helpdesk

Three regimes dominate helpdesk compliance. PCI DSS is the sharpest: a full PAN in a ticket makes the helpdesk part of the cardholder data environment, and a stored CVV is a violation outright — no compensating control permits it. Automated detection and immediate redaction is the recognized way to keep a helpdesk out of PCI scope, and assessors will ask to see it working. GDPR/CCPA apply data minimization, purpose limitation, and subject rights: a five-year-old resolved ticket containing an address and date of birth is stored personal data with no remaining purpose, and it is discoverable by an access request — meaning your DSAR process must search tickets too. Scrub-at-closure plus finite retention shrinks both the risk and the DSAR workload dramatically.

HIPAA reaches any helpdesk where patients discuss care — telehealth support, insurance member services, provider IT desks. PHI in tickets requires access controls, audit trails, and business-associate agreements with the helpdesk vendor and every connected tool. Detection with a health-scoped entity list (MEDICAL_RECORD_NUMBER, DIAGNOSIS, HEALTH_INSURANCE_ID, PRESCRIPTION) lets you find PHI that arrived where it should not, and the HIPAA PHI detection guide maps entity types to the 18 identifiers.

Operationally, encode retention as a ladder the scanner enforces: on ingest, mask never-store data; at closure, mask contextual PII; at retention horizon (say 24 months), either delete or reduce the ticket to its scrubbed analytical skeleton. Each rung is a scheduled job over the helpdesk API using the same detection call, and the ticket tags written at each stage are your audit evidence. Teams running high-volume desks batch the closure pass nightly; API volume pricing for that pattern is on the pricing page.

Support-Desk Scanning Best Practices

Field-tested guidance from helpdesk redaction rollouts.

Scan Before the Ticket Spreads

The window that matters is between ticket creation and the moment agents, integrations, and exports copy the content onward. Scan on the creation webhook, not on a nightly batch — a card number that sat visible for eighteen hours has already been synced to the warehouse and read by a dozen people. Keep the nightly job too, but as a verification sweep that should find nothing.

  • Webhook scan within seconds of creation
  • Cover agent replies and internal notes, not just requester text
  • Nightly sweep as the safety net and metric source

Bring Agents Into the Design

Redaction that surprises agents gets worked around — screenshots, side channels, "please resend your card number". Involve support leads in choosing what stays visible, use typed placeholders so context survives, provide unmask-on-demand for the rare legitimate need, and give agents a macro that tells customers their data was removed for their protection. Redaction then becomes a service agents sell, not a control they fight.

  • Typed placeholders, never blank deletions
  • Audited unmask-on-demand for edge cases
  • Customer-facing macro explaining the redaction

Fix the Inflow, Not Just the Store

Detection metrics tell you where over-sharing originates. If refund forms drive card-number pastes, add a "never send full card numbers" hint and a structured last-4 field. If a chatbot hand-off causes users to repeat their details, pass context through instead. Every inflow you fix reduces scanning volume and risk permanently — the detector's per-channel entity counts are your prioritized to-do list.

  • Dashboard PII inflow per channel and form
  • Product fixes for the top over-sharing triggers
  • Track redactions/week trending toward zero

Frequently Asked Questions

Will scanning delay ticket delivery to agents?
No. The webhook pattern is asynchronous: the ticket is created normally, and the scan-plus-redact completes in one to three seconds afterward — typically before any agent opens it. Detection itself runs in ~150–250 ms; the rest is helpdesk API round trips. For live chat (Intercom), scan each message part as it arrives and edit in place; users see their own message unchanged while the stored copy is masked.
Doesn't Zendesk/Freshdesk already offer PII redaction?
They offer redaction mechanisms — endpoints and agent tools that remove content you point them at — and some pattern-based auto-detection for well-formatted numbers. What they lack is context-aware detection of names, addresses, health details, free-form IDs, and 60+ languages, plus confidence scores to drive graduated policy. The winning combination is the platform's native redaction endpoint as the enforcement arm and the detection API as the brain deciding what to redact.
How do we handle PII in ticket attachments like photographed IDs?
Fetch the attachment from the URL in the webhook payload, extract its text — native text for PDFs and Office files, OCR for images and scans — then run the extracted text through the same detection call. On detection, delete or quarantine the attachment and note the ticket. Passports, driver's licenses, and statements photographed by customers are among the highest-density PII objects in any helpdesk. The complete pipeline with code is in the document scanning guide.
What about the customer's own name and email — should those be masked?
Not in the working view: the requester's contact data is the ticket's addressing metadata and support needs it. Mask it at the boundaries instead — exports, analytics feeds, vendor QA samples, LLM training sets — and at retention horizons. Use custom_instruction to tell the detector to ignore the requester's own name and your support addresses during ingest scans so they do not generate noise, while still catching third-party names in the body.
Can we detect when our own agents paste sensitive data?
Yes, and you should: scan outbound comments and internal notes with the same webhook (Zendesk triggers and Freshdesk automations fire on agent updates too). Agent-originated leaks — pasting a customer's full record into an escalation, or credentials into a note — are less frequent than customer over-sharing but higher severity. Include credential types (PASSWORD, API_KEY, AUTH_TOKEN) in the agent-side scan scope.
Our customers write in many languages. Does detection still work?
Yes — the models detect entities across 60+ languages without per-language configuration, including names, addresses, and national ID formats that regex lists never cover. This matters in support more than almost anywhere else, because ticket language follows your customer base, not your company's working language. The current list is on the supported languages page, and you can paste a non-English ticket into the demo to verify against your own traffic.

Clean Up Your Helpdesk Before the Next Audit

Paste a real (anonymized) ticket into the live demo to see exactly what the API would catch, then wire the webhook in an afternoon. Plans scale from a single desk to millions of tickets.

Try the Live Demo View Pricing