Ingest Freshdesk tickets, replies, private notes, and attachments into CortexDB.

Freshdesk Connector

Captures Freshdesk tickets, their full conversation thread (public replies + private notes), and their attachments as CortexDB experiences. Each item keeps its real source timestamp, so the support timeline is preserved.

Info

Two ways to run this connector:

  • Run it yourself (Free + paid) — pip install cortexdb-connectors then cortexdb-sync sync freshdesk. The connector reads your token from ~/.cortexdb/state.json (written by cortexdb init) or from env vars.
  • Managed sync (Starter and up) — CortexDB runs the same connector as a worker on its infrastructure. Configure it once from your dashboard; CortexDB handles scheduling, retries, backfill, and idempotency.
Warning

Upgrade to 0.2.20. Earlier versions silently lost data: a ticket that reached 10 conversations stopped ingesting new replies forever, a rate-limited detail fetch stored the ticket with no body, and attachments were dropped entirely. See Upgrading from 0.2.19 or earlier.

1. Prepare credentials in Freshdesk

Freshdesk → Profile settings → Your API Key. The connector authenticates as HTTP Basic with the API key as the username and X as the password.

You also need your Freshdesk domain, e.g. acme.freshdesk.com.

2. Configure in your CortexDB dashboard

  1. Sign in at cortexdb.ai/login.
  2. Settings → Connectors → Add Connector → Freshdesk.
  3. Paste the domain and the API key.
  4. Set the scope template. Default: org:<your-org>/source:freshdesk.
  5. Click Start sync.

What gets written

EventEpisode typeNotes
TicketissueSubject + description; content is prefixed [FreshDesk ticket #N]
Public replycommentPrefixed [FreshDesk reply on ticket #N]; tagged reply
Private notecommentPrefixed [FreshDesk note on ticket #N]; tagged note
Ticket or reply attachmentdocumentUploaded to /v1/blobs and ingested as content.kind="blob_ref", so the server's content processors extract and index the file

Every episode carries thread_id = "freshdesk:ticket:<id>", an entity reference of type ticket, and metadata with ticket_id, status, priority and a source_url back to the agent view.

Visibility

All Freshdesk episodes are stored restricted — support data is customer-facing and sensitive. Public replies and private notes land at the same visibility; the distinction is carried by the reply / note tag and the content prefix.

Idempotency and history

Tickets and conversations use a content-versioned key:

ticket:<id>:<updated_at>:<content-digest>
conv:<id>:<created_at>:<content-digest>
freshdesk:att:<attachment_id>

Each edited state is retained as a new version rather than overwriting the last one. The content digest is what makes edited replies and merged tickets work: a Freshdesk merge moves conversations to the primary ticket without changing their id or created_at, so a timestamp-only key would collide and the corrected attribution could never be recorded.

Attachments key on the stable attachment id, so a re-synced ticket replays instead of re-uploading the bytes.

Real-time delivery (webhook)

Freshdesk automations don't sign their payloads, so the receiver authenticates on a static header you configure on the rule.

export FRESHDESK_WEBHOOK_SECRET=<a long random string>
cortexdb-sync serve --port 8081

Then in Freshdesk → Admin → Workflows → Automations, add a Trigger webhook action pointing at POST https://<your-host>/webhooks/freshdesk/events with the header Authorization: Bearer <the same secret>.

The webhook path builds its episode through the same normalizer as polling, so a ticket seen by both serve and sync produces one key and one stored copy at one visibility — running both is safe.

Run it yourself

pip install cortexdb-connectors

# Step 1: get a CortexDB token + actor (one-time, free tier)
pip install cortexdb-cli
cortexdb init

# Step 2: supply the connector's third-party credentials
export FRESHDESK_DOMAIN=acme.freshdesk.com   # required
export FRESHDESK_API_KEY=...                 # required
export FRESHDESK_INCLUDE_CONVERSATIONS=1     # 0 = tickets only, bodies still ingested
export FRESHDESK_INCLUDE_ATTACHMENTS=1       # 0 = skip attachment blobs

# Step 3: one-shot sync, or `watch` for a poll loop
cortexdb-sync sync freshdesk
cortexdb-sync watch freshdesk --interval 300

cortexdb-sync reads your CortexDB token + actor from ~/.cortexdb/state.json automatically. Cursor state is persisted in ~/.cortexdb/sync_state.json, so re-running picks up where the last cycle left off.

Note

FRESHDESK_INCLUDE_CONVERSATIONS=0 drops only the reply and note episodes. The ticket description is always ingested — before 0.2.20 this flag silently discarded ticket bodies too.

Limits and behaviour worth knowing

  • Attachments over 32 MiB are recorded metadata-only: the filename survives in the episode text, the bytes are not uploaded.
  • Rate limits are handled: ticket-detail and conversation requests retry 429 and 5xx responses with Retry-After, up to 5 attempts.
  • A ticket that cannot be fetched holds the cursor and is retried rather than being stored partially. After 5 consecutive failed cycles it is dead-lettered, and cortexdb-sync status reports it.
  • Deletions are not propagated from the poll path. A reply deleted in Freshdesk remains in CortexDB as retained history.

Upgrading from 0.2.19 or earlier

0.2.20 fixed several silent data-loss paths. The one that most likely affects an existing install:

Tickets that reached 10 conversations stopped ingesting. The thread was read from a preview array capped at 10 entries, so once a ticket had 10 replies, every later reply, note and resolution was invisible — while the sync kept reporting success. What was lost was the newest activity: the diagnosis and the fix.

To backfill an affected desk, clear the connector's cursor and re-sync:

cortexdb-sync status                      # note the current cursor first
cortexdb-sync sync freshdesk --since 2020-01-01T00:00:00Z

Re-ingest is safe — idempotency keys dedupe anything already stored.

Because keys now carry a content digest, the first sync after upgrading records one new version per currently-visible ticket state. Steady-state dedupe is unchanged.

See also