Freshservice Integration
Keep Freshservice as your system of record. AscendCore becomes the approval-gated system of action on top: tickets flow in, a human approves in Slack or Teams, the runbook executes, and the decision is written back to the ticket as a private note. No migration.
In one sentence
A Freshservice Workflow Automator rule POSTs new tickets to AscendCore, where the ticket is AI-classified into a runbook, held for explicit human approval, executed against your identity stack, and the outcome lands back on the originating ticket as a private note with an audit-chain reference.
How a ticket flows
- Ingest. Your Workflow Automator rule POSTs the ticket number to
POST /api/v1/tickets/ingestwith a scoped API key. AscendCore then reads the ticket's subject, description, and requester from your Freshservice with the key you connected. One ticket is ingested at most once: retries and rule re-fires return the original run instead of posting a second card. - Classify. AscendCore classifies the ticket text into a runbook intent with a confidence score. Low-confidence or unrecognized tickets are received, audit-logged, and left alone (
no_action). Nothing executes on a guess. - Approve. The matching runbook's approval is staged where your organization's approval routing says: a card in your Slack or Teams admin channel, with a threaded note showing the originating ticket number, a link back to it, and the AI's classification and confidence; or, when your organization has no chat channel or chose it, the approval queue in the admin portal, with approvers emailed. A human approves or denies. Every runbook is approval-gated. There is no autonomous path.
- Execute. On approval, the runbook runs against your stack (Okta, Entra ID, M365) and the action is recorded on the org's tamper-evident SHA-256 audit chain.
- Write back. The decision (approved or denied, by whom, at what time) is appended to the originating ticket as a private note. Your ITSM keeps the complete story.
Step 1: Connect Freshservice
In the admin portal under Integrations, open Freshservice and enter:
- Base URL: your account address,
https://yourcompany.freshservice.com. Use the freshservice.com address even if your service desk has a custom domain: the Freshservice API only answers there. - API key: the key of the Freshservice account AscendCore acts as. It is found under Profile settings. Freshservice keeps API keys turned off until an admin enables one: in Admin, open Agents, select the agent, and turn on API key on its Permissions tab. Least privilege applies: the account needs to view tickets and add private notes. A dedicated integration user (a non-interactive account that uses no agent license) keeps the notes from appearing under a person's name.
Save & verify stores the key in your organization's encrypted vault and reads one ticket to prove the key works. The key is never shown again, and it is used only for your organization. Adding a note is not tested by Verify, because that would change a ticket.
Step 2: Create a scoped AscendCore API key
In the admin portal under API, create a key with the ticket:ingest scope. Least privilege applies: a key with only this scope can submit tickets but cannot execute runbooks directly, read audit rows, or browse the catalog.
You will paste this key into the Automator rule's webhook headers in the next step. It is stored inside the rule, visible only to Freshservice admins who can edit workflows.
Step 3: Add the Workflow Automator rule
In Admin, then Workflow Automator, create an event-based ticket workflow. Drag an Action node after the event and choose Trigger Webhook:
- Event: Ticket is raised. Add a condition (for example, category or group) if you only want a subset of tickets routed to AscendCore.
- Action: Trigger Webhook, with:
- Request type: POST
- URL:
https://ascendcore.ai/api/v1/tickets/ingest - Authentication: leave Credentials empty, tick Custom headers, and add the header
Authorizationwith the valueBearer ak_live_.... AscendCore only accepts the Bearer form, so do not use the API key credential type. - Encoding: JSON, with Content set to Advanced, and the body below
{
"source": "freshservice",
"ticket": {
"ticketId": "{{ticket.id_numeric}}"
}
}Only the ticket number is sent. Workflow Automator inserts placeholder values into the body as they are, so a quote or a line break in a subject or description would make the body invalid JSON. AscendCore reads those fields from your Freshservice instead, which also means a request can only refer to a ticket that exists in your account. {{ticket.id_numeric}} is the number the Freshservice API uses; {{ticket.id}} (for example INC-42) is accepted too. Raise a test ticket to confirm a 200 before relying on the rule.
Payload reference
| Field | Required | Purpose |
|---|---|---|
source | Yes | Must be "freshservice" for this integration. |
ticket.ticketId | Yes | Ticket number (string or number), or the display id such as INC-42. Idempotency key and write-back target. |
ticket.subject | Only without a connection | Primary classification input. Read from Freshservice when it is connected. |
ticket.description | No | Appended to the classification input. Read from Freshservice when it is connected. |
ticket.url | No | Link back to the ticket, shown to the approver. Built from your connected address when Freshservice is connected. |
ticket.requesterEmail | No | Target-user fallback when the ticket text names nobody else. Read from Freshservice (the requester's primary email) when it is connected. |
Supported intents in this release
| Intent | Runbook | System |
|---|---|---|
| MFA reset | RB-002 | Okta |
| Password reset | RB-001 | Okta |
| Offboarding (suspend account) | RB-011 | Okta |
| Account unlock | RB-007 | Microsoft Entra ID |
| M365 license assignment (a SKU named in the ticket is honored) | RB-006 | M365 |
| Group add / remove (group named in the ticket) | RB-013 | Microsoft Entra ID |
| New hire provisioning (name and email in the ticket; named groups and license tier honored) | RB-003 | Okta + M365 |
Tickets that classify to anything else are acknowledged with no_action and audit-logged. Additional intents arrive as additive extensions; your Automator rule does not change.
What the write-back looks like
When the approval reaches a decision (from Slack, Teams, or the dashboard), AscendCore posts a private note on the originating ticket:
[AscendCore] Runbook decision: APPROVED
Runbook: MFA Reset
Target user: Sarah Chen (sarah.chen@acme.com)
Decision by: slack:U0123APPROVER at 2026-06-09T17:42:11.310Z
Completed. The action ran after explicit human approval.
AscendCore run: mfa_1781026931_310000
This decision is recorded on the org's tamper-evident audit chain in AscendCore.Denied requests get a note too, stating that no changes were made. If the run only partly finished, was stopped by a safety check, or failed, the note says so; it never claims an action that did not run. The note is private (agent-facing, off the requester thread) and is posted with the Freshservice API key your organization connected under Integrations (it needs to add private notes; least privilege applies). The write-back is deliberately non-blocking: if your workspace is unreachable, the approval flow itself is unaffected and the decision remains on the AscendCore audit chain.
Limits and troubleshooting
- Freshservice plans. Workflow Automator and API access are part of every Freshservice plan, and the free trial includes them.
- API rate limits. Freshservice limits API calls per account per minute, shared with your other integrations. Reading an ingested ticket with its requester counts as two calls, and each decision's note counts as one. When Freshservice asks AscendCore to slow down for a few seconds, it waits and tries once more; otherwise the note is skipped and the decision stays on the audit chain.
- Webhook retries. Freshservice retries a webhook that does not answer with a 2xx. A retry of a ticket AscendCore already took returns the original run, so no second card is posted.
400 invalid_payload: the body was not valid JSON or had no ticket number. Use the body above, with no free-text placeholders.422 ticket_not_found: the connected Freshservice account has no ticket with that number. Check that the rule and the connection point at the same account.503 itsm_unavailable: Freshservice could not be reached to read the ticket. Freshservice retries the webhook on its own.
Security model
- Scoped bearer auth. The integration reuses AscendCore's standard API-key model. Keys are salted-hashed at rest, scope-gated, rate-limited, and revocable from the dashboard.
- Approval-gated, always. Ingested tickets follow the same human-in-the-loop policy as every other trigger. AI classifies; it never executes.
- Audit-chained. Every ingestion (including no-action dispositions) and every decision is recorded on the org's tamper-evident SHA-256 audit chain, usable as evidence for SOC 2 and ISO 27001 reviews.
- Idempotent. Duplicate submissions of the same ticket return the original run. Retry storms cannot flood your approval channel.
- Sandbox-aware. If your org is in Sandbox Mode, ingested tickets exercise the full approval flow with simulated execution. The note on the ticket is labeled
[AscendCore Sandbox], says nothing was changed in your identity systems, and is written only through the Freshservice connection your org added itself.
Full API reference
The complete request/response contract for POST /api/v1/tickets/ingest (dispositions, error codes, schemas) lives in the OpenAPI reference. Using ServiceNow or Jira instead? See the ServiceNow guide or the Jira guide.
