Guide to Freshdesk Webhooks: Features and Best Practices
Freshdesk is Freshworks' customer support helpdesk. Each of its webhooks is an action inside an automation rule. When a ticket is created or updated and the rule's conditions match, Freshdesk calls your URL with a body you design. That makes them flexible enough to drive SMS alerts, inventory updates or a product management sync, and means you decide every part of the request.
This guide covers how Freshdesk webhooks are configured, how to shape the body and authenticate the request, how retries and limits behave, and the best practices for running a Freshdesk receiver in production.
What are Freshdesk webhooks?
A Freshdesk webhook is the Trigger Webhook action in an automation rule that runs on ticket creation or on ticket updates. The rule's conditions decide when it fires, and the action decides the method, URL, headers and body. Freshdesk documents it on the Growth, Pro and Enterprise plans.
Freshdesk doesn't sign webhooks, and there's no fixed payload or event name. You write the body yourself, using placeholders such as {{ticket.id}} that Freshdesk fills in when the rule runs. To authenticate the request, add a custom header holding a secret and check it on your endpoint.
Freshdesk webhook features
| Feature | Details |
|---|---|
| Configuration | Admin > Workflows > Automations > Ticket Creation or Ticket Updates > New Rule > Trigger Webhook |
| Trigger | The rule's own conditions, on ticket creation or ticket update |
| Methods | GET, POST, PUT, PATCH, DELETE |
| URL | Can include placeholders, such as {{ticket.id}} |
| Encoding | JSON, XML or XML-Encoded |
| Body | Simple Content (pick ticket properties) or Advanced (custom body with placeholders) |
| Signature | None |
| Authentication | A custom header you define, or the Requires authentication toggle |
| Success | 200 to 299. 300 to 399 is followed as a redirect |
| Retries | Once every 30 minutes, 48 calls in total |
| Rate limit | 1000 webhook calls an hour. Further calls are buffered |
| Failure alerts | Account administrators receive an email |
| Source IPs | Not documented |
Common events
Freshdesk has no event catalogue for webhooks. An automation rule on the Ticket Creation tab fires when a new ticket matches its conditions, and a rule on the Ticket Updates tab fires when an update matches. Freshdesk's own examples:
| Scenario | Rule conditions | What the webhook calls |
|---|---|---|
| Send an SMS when a customer replies | Customer replies to the ticket or adds a comment | A third-party SMS tool, with the comment |
| Update inventory for product returns | A custom category field changes to "Product Return" | The store inventory system |
| Sync feature request status | Status changes on a "Feature Request" ticket | An internal product management tool |
| Raise an alarm on bad feedback | A satisfaction rating of "Not Good" arrives | A smart bulb and siren |
Because you write the body, you decide what identifies the event. The {{Triggered event}} placeholder "returns the name of the event that triggered the rule", but Freshdesk doesn't list its possible values, so a hard-coded name per rule is easier to rely on. An Advanced JSON body for a ticket-created rule might look like this:
{
"event": "ticket_created",
"triggered_event": "{{Triggered event}}",
"ticket_id": {{ticket.id}},
"requester_email": "{{ticket.requester.email}}"
}
event here is your own convention, not a Freshdesk field. Use the same body shape on your Ticket Updates rule with "event": "ticket_, and your receiver can route on it.
Setting up Freshdesk webhooks
- Go to Admin > Workflows > Automations. In Freshdesk Omni, it's Admin Settings > Configuration and Workflows > Ticket Automations.
- Choose the Ticket Creation or Ticket Updates tab and click New Rule.
- Name the rule and set its conditions.
- Under the actions, select Trigger Webhook.
- Choose the request type. Use
POSTto send a body. - Enter your callback URL.
- Toggle Add custom headers and add your secret header (see below).
- Choose JSON encoding, then Simple Content to pick ticket properties or Advanced to write the body.
- Save the rule.
Trigger Webhook is for calls whose response you don't need. If an automation must use the response, Freshdesk offers a separate Trigger API action on Pro plans and above.
Securing Freshdesk webhooks
Generate a long random secret with no spaces, for example with openssl rand -hex 32. In Add custom headers, enter it as a header-value pair:
X-Freshdesk-Webhook-Secret: 3f6b0c2e9a...
Freshdesk's header rules are strict. Each header goes on its own line in the form X-Sample-CustomHeader1: VALUE, header names are case-insensitive, values are kept as they are, and "the trigger will not be executed if there are spaces in between". That last rule is why this guide uses a dedicated header with a hex value rather than Authorization: Bearer <token>, which has a space in its value. Test a delivery if you choose a value with spaces.
The Requires authentication toggle asks for an API key and is meant for calling APIs such as Freshdesk's own. Freshdesk doesn't document how it sends that key, so a custom header is the clearer choice for your endpoint.
Check the header in constant time:
const crypto = require('crypto');
const express = require('express');
function verifyHeader(value, expected) {
if (typeof value !== 'string' || !expected) return false;
const received = Buffer.from(value, 'utf8');
const wanted = Buffer.from(expected, 'utf8');
return received.length === wanted.length && crypto.timingSafeEqual(received, wanted);
}
const app = express();
app.post('/webhooks/freshdesk', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.FRESHDESK_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
if (!verifyHeader(req.headers['x-freshdesk-webhook-secret'], secret)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString('utf8'));
// Route on event.event, then process asynchronously.
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
def verify_freshdesk_header(value) -> bool:
secret = os.environ.get("FRESHDESK_WEBHOOK_SECRET")
if not secret or not value:
return False
return hmac.compare_digest(value.encode(), secret.encode())
A header secret proves the caller knows it, but nothing is signed over the body and the same header arrives on every request. Serve the endpoint over HTTPS only.
Make Freshdesk webhooks production-ready. Hookdeck Event Gateway checks your secret header on every Freshdesk call, answers with a
2xxstraight away so Freshdesk's retries and hourly limit aren't spent on your downtime, and queues each ticket event for your service.
Freshdesk webhook limitations and pain points
Retries are slow and end after a day
The Problem: A short outage on your side delays ticket updates by half an hour or more, and a long one loses them.
Why It Happens: Any status outside 200 to 399 fails the call, and Freshdesk retries "once every 30 minutes, totaling 48 calls". That's about a day of attempts with nothing faster in between.
Workarounds:
- Return a
2xxas soon as the request is stored, and process it afterwards. - Watch for the failure emails Freshdesk sends to account administrators.
- Reconcile tickets with the Freshdesk API after an outage.
How Hookdeck Can Help: Hookdeck accepts each Freshdesk call immediately and stores it, so Freshdesk sees a success and doesn't fall into its 30-minute retry cycle. Hookdeck then delivers to your service with its own retries, and you can replay anything that failed.
The hourly limit buffers busy periods
The Problem: During a spike, webhooks arrive late, sometimes an hour after the ticket changed.
Why It Happens: Freshdesk limits webhooks to 1000 calls an hour. Calls beyond that are buffered "until fresh calls are available after 1 hour".
Workarounds:
- Tighten rule conditions so only the changes you act on trigger a webhook.
- Avoid several rules calling webhooks for the same update.
- Keep your endpoint healthy, so failed calls don't add to the queue.
How Hookdeck Can Help: Hookdeck accepts each Freshdesk call without delay, so failures on your side don't add retried calls to a busy hour, and its delivery rate limits stop a burst of buffered calls from overwhelming your service when they're released.
No event IDs, no standard payload
The Problem: You can't tell a retried call from a new event, or one rule's request from another's.
Why It Happens: Freshdesk sends only the body you wrote. There's no delivery ID, no event ID and no timestamp unless you add placeholders for them, and {{Triggered event}} values aren't documented.
Workarounds:
- Put a fixed
eventname in each rule's body. - Include
{{ticket.id}}and, for idempotency, the ticket fields you act on, so a repeat can be recognised. - Point each rule at its own path, such as
/webhooks/freshdesk/ticket-created.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries based on fields in the body, and filters can route each rule's events by your event field.
Header formatting can silently stop the rule
The Problem: You add an authentication header and the webhook never fires.
Why It Happens: Freshdesk says the trigger "will not be executed if there are spaces in between", and values with several parts need delimiters other than commas and colons.
Workarounds:
- Use a header whose value is a single token with no spaces, such as a hex string.
- Test the rule with a real ticket after any header change.
How Hookdeck Can Help: Hookdeck's Freshdesk source type checks a header name and value you choose, compared exactly. Set the header name to X-Freshdesk-Webhook-Secret and the value to your secret. Verification is optional on the source, so leave it off if you didn't set a secret.
Best practices
Write a consistent body for every rule
Give every rule the same JSON shape with your own event field, the ticket ID and the fields your handler needs. Quote placeholders that produce text, so the body stays valid JSON when a value is empty.
Compare the header in constant time
Use crypto.timingSafeEqual (after a length check) or hmac.compare_digest, and fail closed when the secret isn't configured.
Make handlers idempotent
A call that timed out on Freshdesk's side but reached you will be sent again 30 minutes later. Key your writes on the ticket ID and the state you're applying. See our guide to webhook idempotency.
Acknowledge first, process after
Freshdesk webhooks run asynchronously to automations and nothing waits on your response, so store the request, return 2xx, and do the work afterwards. See why to process webhooks asynchronously.
Watch for loops
Webhooks use the Agent API and "are treated as an agent performing an update". If your handler updates the ticket through the API, make sure that update doesn't match the same rule and fire the webhook again.
Conclusion
Freshdesk webhooks are automation rule actions with a body you design and no signature. Give each rule a consistent JSON body with your own event name, authenticate with a no-space secret header compared in constant time, respond with a 2xx straight away, and keep in mind that failed calls are retried only every 30 minutes, within a limit of 1000 calls an hour.
Hookdeck Event Gateway checks each Freshdesk call, stores it, and retries into your service on your schedule.
Get started with Hookdeck for free and handle Freshdesk webhooks reliably in minutes.