# Freshservice Integration

Open Freshservice tickets from Capsule Security policy violations, so AI agent and shadow-AI signals land in the service desk your team already works out of.

## Overview

When a policy you've linked is violated, Capsule creates a ticket in your Freshservice account via the **Create Ticket API**. The ticket carries the violation's severity, policy, affected entity, and time, plus a **View in Capsule** link back into the portal. From there it follows your normal service-desk workflow: assignment rules, SLAs, escalations, and reporting all apply as they would to any other ticket.

Capsule authenticates with a Freshservice **API key** over HTTP Basic auth. The key belongs to a Freshservice agent and inherits that agent's permissions, so the integration can do exactly what that agent can do and nothing more.

```
Capsule policy violation
  → Capsule calls POST /api/v2/tickets on your Freshservice domain
  → ticket opens under the requester you configured
  → your Freshservice assignment rules, SLAs, and workflows take over
```

Two values are **account-level** and shared by every Freshservice destination you create: the **domain** and the **API key**. The **requester email** is per-destination. That means you can point several destinations at the same Freshservice account under different requesters (one per team, say) and link each to different policies, while changing the domain or key re-points all of them at once.

## Prerequisites

Before you begin, ensure you have:

- A **Freshservice** account, and its domain in the form `yourcompany.freshservice.com`
- A Freshservice **agent** who is allowed to **create tickets** and **view tickets**, whose API key you can read
- An existing Freshservice **requester** whose email address tickets will be filed under
- A **Capsule Security** account with admin access


## 1. Get the API key from Freshservice

The API key is per-agent, not per-account, and it inherits that agent's permissions.

1. Sign in to Freshservice as the agent whose key you want to use.
2. Open the **avatar menu** in the top right and choose **Profile settings**.
3. Copy the key from the **API Key** panel on the right.


> **Use an agent that can create *and* view tickets.** Capsule searches your tickets by tag before creating one (see [Duplicate protection](#duplicate-protection)), so a key limited to creating tickets fails on the search step. A service account dedicated to the integration is a good fit: its permissions stay stable, and revoking it later doesn't disturb a person's own access.


Capsule stores the key in **Google Secret Manager**, encrypted and scoped to your tenant. It is never written to Capsule's database and never returned to the browser, which is why the edit screen shows only **API key configured** rather than the value itself.

## 2. Configure the destination in Capsule

1. Log in to the **Capsule Security** portal.
2. Go to **Settings → Notifications**.
3. Click **Add channel** and choose **Freshservice**.
4. Fill in the four fields:
| Field | What it is |
|  --- | --- |
| Display name | How this destination appears in Capsule. Naming it after the team that owns the queue helps. |
| Freshservice domain | Your account host, `yourcompany.freshservice.com`. Pasting a full portal URL is fine - Capsule normalizes it down to the host. |
| Requester email | The Freshservice requester tickets are filed under. It must already exist in Freshservice. |
| API key | The key from step 1. |
5. Click **Save & Test**.


Only `*.freshservice.com` hosts are accepted, on both the client and the server. A custom vanity domain or a full path won't validate.

Each requester email can back **one** Freshservice destination per Capsule tenant. If you re-use one, Capsule tells you inline rather than creating a second destination that would file into the same place.

### What the test actually proves

**Save & Test** creates a **real ticket** in your Freshservice account through the same code path a live violation uses: the API key is exchanged for a Basic auth header, the ticket is posted to `POST /api/v2/tickets` on your domain, and Freshservice's response decides the outcome. A passing test therefore confirms end to end that the domain resolves, the key is valid, the agent may create tickets, and the requester is accepted.

The test ticket is clearly marked:

- **Subject:** `[TEST] Capsule Freshservice test ticket`
- **Priority:** Low
- **Body:** names the destination it was testing and says it's safe to close


It is deliberately left **untagged**, so it stays out of the `capsule-security` view you'll build in [Verify tickets are arriving](#verify-tickets-are-arriving). Close it or delete it, whichever suits your service desk.

> A passing test proves Freshservice **accepted and created** the ticket. Open your service desk to confirm it landed where you expected - assignment rules or a workspace routing rule can put it somewhere other than the queue you had in mind.


## 3. Link the destination to policies

A Freshservice destination only opens tickets for the policies you link to it. On the **policy edit** screen, add the destination to the policy's notification channels, the same way you link any other destination. Link as many policies as you want; a destination with no linked policies stays idle.

Because tickets carry real operational cost, this is worth being deliberate about. Linking every policy to a Freshservice destination turns every low-severity signal into a ticket someone has to close. A common split is to send high and critical policies to Freshservice and leave the rest on a chat or email destination.

## What the tickets contain

Each ticket opens with status **Open** and maps the violation's severity onto Freshservice's priority scale:

| Capsule severity | Freshservice priority |
|  --- | --- |
| Critical | Urgent |
| High | High |
| Medium | Medium |
| Low and informational | Low |


The **subject** is the violation title, truncated to Freshservice's 255-character limit. The **description** is rendered HTML containing the violation summary, then the policy, affected entity, severity, and timestamp, the Capsule issue identifier, and a **View in Capsule** link.

Every real ticket is tagged **`capsule-security`**, which is what makes a single service-desk view of everything Capsule opened possible. Tickets raised for the same Capsule issue additionally share a `capsule-issue-` tag, so an issue's tickets stay correlated as new evidence arrives. The tag values are hashed because raw Capsule identifiers overflow Freshservice's 32-character tag limit.

### Verify tickets are arriving

In Freshservice, filter your tickets by the tag `capsule-security`. Every ticket Capsule has opened carries it, so this is the one view worth saving. If it's empty right after setup, confirm a linked policy has actually been violated before troubleshooting - a destination with no linked policies, or a policy that hasn't fired, produces no tickets by design.

## Duplicate protection

Before creating a ticket, Capsule searches your tickets for a tag unique to that one delivery attempt. If a previous attempt already opened the ticket but failed before Capsule recorded it - a timeout after Freshservice committed the write, for example - the retry finds the existing ticket and skips creating a second one.

This is scoped per delivery, not per issue. One Capsule issue legitimately produces more than one ticket over its lifetime as new evidence arrives, and two destinations linked to the same policy each get their own ticket. What the check prevents is the *same* delivery landing twice.

A failed search blocks the create rather than falling through to it. Creating without the check would reintroduce duplicate tickets on exactly the transient errors the check exists to absorb, and the delivery is retried anyway.

## Rate limits

Freshservice enforces its own per-account API rate limits and answers a request over the limit with `429` and a `Retry-After` hint.

Capsule honors short hints in place: a wait of up to **10 seconds** is absorbed and the request retried. Longer hints - Freshservice can ask for 25 minutes - are not absorbed, because policy evaluation waits on the delivery and a worker would hold a slot for the whole sleep. Those surface as a rate-limited failure and redelivery backs off in Capsule's queue instead.

Each request also has a **30 second** timeout. Timeouts and transient network faults are treated as retryable.

## Rotating the API key

Rotate the key in Freshservice, then in Capsule open **Settings → Notifications**, **Edit** any Freshservice destination, click **Replace API key**, paste the new key, and **Save & Test**.

Because the key is account-level, replacing it on one destination replaces it for all of them - you don't repeat this per destination. Leaving the field alone keeps the stored key, so editing a display name or requester never requires re-entering the key.

## Troubleshooting

**`Freshservice rejected the API key` (401 / 403).** The key is wrong, expired, or belongs to an agent who can't create tickets. Confirm you copied the whole key, and confirm the agent has both create and view permission on tickets. Replace the key and test again.

**`Check your Freshservice details` (400 / 404).** Freshservice rejected the ticket itself. The usual cause is a requester email that doesn't exist in Freshservice - the address has to match an existing requester, Capsule doesn't create one. A `404` also points at a domain that resolves but has no tickets endpoint, so re-check the domain against your account.

**The test passes but no ticket appears in the queue you expected.** The ticket was created; something in Freshservice routed it. Check your assignment rules and, on multi-workspace accounts, which workspace the requester belongs to. Filtering by tag `capsule-security` will find it wherever it landed.

**The destination row shows an Error state.** Capsule flips a destination to **Error** the first time ticket creation fails for it, and shows the reason on the row. If it's a rate limit (see [Rate limits](#rate-limits)) no action is needed; delivery resumes when the window resets. Otherwise **Edit** the destination to correct the domain, requester, or key. The next successful delivery clears the error and returns the destination to Active - and so does a passing **Save & Test**, which updates the destination's health without counting as a real delivery.

## Support

For help with this integration:

- **Email**: support@capsule.security
- **Include**: Your organization ID, your Freshservice domain, the affected destination's display name, and any error shown on the destination row


## References

- [Freshservice API: Tickets](https://api.freshservice.com/#tickets)
- [Freshservice API: Authentication](https://api.freshservice.com/#authentication)
- [Freshservice API reference](https://api.freshservice.com/)