Connect Bubble to Freshdesk using the API Connector with Basic Auth — your Freshdesk API key in the username field and the literal character X as the password, base64-encoded together. Build a white-labeled customer support portal where users submit, view, and track their own tickets entirely inside Bubble, bypassing Freshdesk's native portal customization limits.
| Fact | Value |
|---|---|
| Tool | Freshdesk |
| Category | CRM & Sales |
| Method | Bubble API Connector |
| Difficulty | Beginner |
| Time required | 1–2 hours |
| Last updated | July 2026 |
Building a white-labeled Freshdesk support portal in Bubble
Freshdesk's API authentication has the most distinctive quirk in the support tool category: instead of a standard Authorization header with a key, Freshdesk uses HTTP Basic Auth where the **API key is the username** and the **literal character X is the password**. Freshdesk's own documentation describes this as 'using X as a fake password.' The combination `api_key:X` is base64-encoded and placed in the Authorization header.
In Bubble, there is no native Basic Auth toggle in the API Connector, so you pre-encode the credentials once using any base64 encoder and paste the resulting string directly into a custom Authorization header marked Private.
The primary Bubble use case for Freshdesk is a custom customer-facing support portal. Freshdesk's native portal customization has limited branding controls — you cannot fully match your brand identity. With Bubble, you build a fully branded portal where customers log in (via Bubble's own auth), see only their own tickets, submit new requests, and track status updates. The Freshdesk API handles all the ticketing logic behind the scenes.
For inbound events (ticket created in Freshdesk → trigger Bubble automation), Freshdesk can send webhook POST requests to a Bubble Backend Workflow endpoint. This requires a Bubble paid plan. Rate limits on Freshdesk's free and Growth plans (40 req/min) are low — a Bubble page polling for ticket updates every 10 seconds for 5 simultaneous users will exceed this immediately. Use the inbound webhook path for real-time updates rather than polling.
Integration method
Bubble API Connector with Freshdesk Basic Auth — API key as username and literal 'X' as password, base64-encoded and stored in a Private Authorization header. No OAuth required. Backend Workflow exposes an endpoint for receiving inbound Freshdesk webhook events (paid Bubble plan required for Backend Workflows).
Prerequisites
- A Freshdesk account — the API is available on all paid plans (Growth, Pro, Enterprise) and the free 3-agent plan. Get your API key from Profile Settings → API Key
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble → Install)
- A base64 encoder (your browser's developer console, any online encoder, or the Node.js Buffer.from('apikey:X').toString('base64') command) to pre-encode your credentials
- For inbound Freshdesk webhooks: a Bubble paid plan (Starter or above) — Backend Workflows are not available on the Free plan
Step-by-step guide
Get your Freshdesk API key and encode your credentials
Log in to your Freshdesk account at yourcompany.freshdesk.com. Click on your profile picture in the top-right corner and select 'Profile Settings.' Scroll down to find the 'Your API Key' section — it displays an alphanumeric string like `abc123xyz456def789`. Copy this key. Now you need to base64-encode the combination of your API key and the literal letter X, separated by a colon: `your_api_key:X`. The letter X is uppercase and is exactly one character — it is Freshdesk's documented authentication pattern for API token usage (as opposed to password-based auth). Encoding with just the key and no password is not supported — you must include the `:X` suffix. To encode: - In your browser's developer console (F12 → Console), type: `btoa('your_api_key:X')` and press Enter — this gives the base64 string. - Alternatively, use any online base64 encoder: encode the string `your_api_key:X`. The result is a base64 string that looks something like `YWJjMTIzeHl6NDU2ZGVmNzg5Olg=`. This is the value you will paste into Bubble. Keep both the original API key and the base64-encoded string — you will need the base64 version for the Bubble API Connector.
1// Base64 encoding your Freshdesk credentials:2// Your API key example: YourApiKey1233// Combined string: YourApiKey123:X (note the colon and uppercase X)4// Base64 result: encode('YourApiKey123:X')56// Browser console method:7btoa('YourApiKey123:X')8// Result: 'WW91ckFwaUtleTEyMzpY'910// The Authorization header value will be:11// Basic WW91ckFwaUtleTEyMzpY1213// Your Freshdesk base URL:14https://yourcompany.freshdesk.com/api/v215// Replace 'yourcompany' with your actual Freshdesk subdomainPro tip: Store your raw Freshdesk API key securely (e.g., in a password manager) alongside the base64-encoded version. If you regenerate the API key in Freshdesk, you must re-encode the new key and update the base64 string in Bubble's API Connector header.
Expected result: You have your Freshdesk API key from Profile Settings and a base64-encoded string of `api_key:X` ready to paste into Bubble.
Configure the Bubble API Connector with the Freshdesk Authorization header
In your Bubble editor, go to Plugins in the left sidebar. Click 'Add another API' and name it 'Freshdesk.' Under the API group settings, set the base URL to `https://yourcompany.freshdesk.com/api/v2` — replace `yourcompany` with your actual Freshdesk subdomain (visible in your browser URL when logged in to Freshdesk). Add a shared header at the API group level: - Header name: `Authorization` - Header value: `Basic ` followed by your base64-encoded string from the previous step Example: `Basic WW91ckFwaUtleTEyMzpY` - Check the **Private** checkbox next to this header Also add a shared header: - Header name: `Content-Type` - Header value: `application/json` Now add your first call. Click 'Add another call' and name it 'Get Tickets.' Set the method to GET and the URL to `/tickets` (relative to the base URL). Add URL parameters: - `filter`: text, with a default value of `open` (options: open, pending, resolved, all_tickets) - `page`: number, default 1 - `per_page`: number, default 30 Click 'Initialize call.' Leave the filter as 'open' and click 'Send.' Freshdesk returns an array of ticket objects — Bubble will detect the response array. Note that the response is a direct array (not wrapped in a key), so set 'Use as' to 'Data' and the list type to 'array of items.' If Initialize call returns 'There was an issue setting up your call,' double-check your base64 encoding. A common error is missing the space after `Basic ` in the header value, or encoding `api_key:x` with lowercase x instead of uppercase X.
1// API Connector: Freshdesk → Get Tickets2// Method: GET3// Base URL: https://yourcompany.freshdesk.com/api/v24// Call URL: /tickets5//6// Shared headers (at API group level):7// Authorization: Basic <base64(api_key:X)> // mark Private8// Content-Type: application/json9//10// URL parameters:11// filter: open (or pending, resolved, all_tickets)12// page: 1 (pagination)13// per_page: 30 (max 100 per Freshdesk docs)14//15// Example response (array, not wrapped in a key):16// [17// {18// "id": 12345,19// "subject": "My order hasn't arrived",20// "status": 2,21// "priority": 1,22// "requester_id": 6789,23// "created_at": "2026-07-10T09:00:00Z",24// "updated_at": "2026-07-10T11:30:00Z",25// "tags": ["billing", "urgent"]26// }27// ]Pro tip: The Freshdesk `status` field uses numeric codes: 2=Open, 3=Pending, 4=Resolved, 5=Closed. In your Bubble UI, map these numbers to labels using a Conditional on Text elements or a Bubble Option Set (create options Open/Pending/Resolved/Closed each with a matching numeric value). Do not display the raw number to end users.
Expected result: The Freshdesk API Connector group is configured with the Private Authorization header. The Get Tickets call initializes successfully and Bubble detects the array of ticket objects.
Create tickets from a Bubble form with POST /tickets
The ticket creation endpoint is the most important write call for a customer portal — customers submit new support requests through a Bubble form and the ticket appears in Freshdesk automatically. Add a new call under the Freshdesk API group: name it 'Create Ticket,' method POST, URL `/tickets`. The JSON body must include these fields: - `subject`: text (required) — the ticket title - `description_html`: text (required) — the ticket body **as HTML**. Note: `description` (plain text) is deprecated in v2; always use `description_html`. Even if your content is plain text, wrap it in `<p>your text</p>` tags. - `email`: text (required) — the requester's email address - `priority`: number (required) — 1=Low, 2=Medium, 3=High, 4=Urgent - `status`: number (required) — 2=Open is the correct starting state for new tickets Optional fields: - `tags`: list of text strings for categorization - `type`: text — custom ticket type defined in your Freshdesk settings In your Bubble form page, add Text Input fields for Subject and Description, and a Dropdown for Priority. Wire the Submit button to a Backend Workflow that calls Create Ticket, passing the form values as dynamic parameters. Store the returned `id` in the User record or a Custom State so the UI can immediately show the new ticket without a full page refresh. To show the new ticket to the customer right away, add a 'Refresh the page' or update a Custom State list to append the new ticket object to the existing displayed list.
1// API Connector: Freshdesk → Create Ticket2// Method: POST3// URL: /tickets (relative to base URL)4// Authorization: Basic <base64(api_key:X)> // shared Private header5// Content-Type: application/json6//7// JSON body:8{9 "subject": "<dynamic_ticket_subject>",10 "description_html": "<p><dynamic_ticket_description></p>",11 "email": "<dynamic_requester_email>",12 "priority": 2,13 "status": 214}1516// Note: 'description' (plain text) is deprecated in v2.17// Always use 'description_html' — even for plain text wrap in <p> tags.1819// Priority codes: 1=Low, 2=Medium, 3=High, 4=Urgent20// Status codes: 2=Open, 3=Pending, 4=Resolved, 5=Closed2122// Successful response includes:23// {24// "id": 67890,25// "subject": "...",26// "status": 2,27// "created_at": "2026-07-10T12:00:00Z"28// }Pro tip: When building the `description_html` value dynamically in Bubble, do not use angle brackets in the free text from your Text Input — HTML inside HTML can break the JSON. If the description field accepts rich text, sanitize it before passing. For plain-text support forms, wrap the Text Input value in a simple `<p>` tag on Bubble's text concatenation: `'<p>' + description_input's value + '</p>'`.
Expected result: Submitting the Bubble form creates a real Freshdesk ticket. The response returns the new ticket ID. The ticket is visible in the Freshdesk agent dashboard under the Open filter.
Build a customer-facing ticket list with filtering and pagination
The core of your customer support portal is a list of the current customer's tickets. Freshdesk returns tickets by filter, so you need to filter by the requester's email to show only the logged-in user's own tickets. Add a call 'Search Tickets' using GET `/search/tickets` with the query parameter: - `query`: `"requester_id:6789"` — or use the email-based search: `"requester:[email]"` where `[email]` is the current user's email address surrounded by single quotes. Example URL: `/search/tickets?query="requester:'customer@example.com'"` This returns a search results object with `results` (array of tickets) and `total` (count). In Bubble, initialize this call with a real email that has existing tickets to get Bubble to detect the `results` array correctly. Bind a Repeating Group to this call's `results` array. Inside each row, display: - Subject (text element) - Status badge: use a Conditional on a Group's background color — green for Resolved, yellow for Pending, red for Open - Priority badge: similar conditional coloring - Created date formatted as 'MMM D, YYYY' - A 'View Details' button that navigates to a detail page, passing the ticket ID as a URL parameter For pagination, Freshdesk's search endpoint does not use page/per_page params — it returns up to 30 results per request by default. For the filter-based GET /tickets endpoint, use `page` and `per_page` params and add 'Previous' and 'Next' buttons that increment/decrement a page number Custom State. Because Freshdesk's free and Growth plans allow only 40 requests per minute, avoid automatic refresh timers. Instead, show a 'Refresh' button that lets the customer manually reload the ticket list.
1// API Connector: Freshdesk → Search Customer Tickets2// Method: GET3// URL: /search/tickets4// Query parameter:5// query: "requester:'customer@example.com'"6// (single quotes around email are required by Freshdesk search syntax)7//8// Example constructed URL:9// https://yourcompany.freshdesk.com/api/v2/search/tickets10// ?query="requester:'jane@example.com'"11//12// Response:13// {14// "results": [15// { "id": 123, "subject": "...", "status": 2, ... }16// ],17// "total": 518// }1920// For paginated list (GET /tickets with filter):21// URL: /tickets22// Parameters:23// email: <current_user's_email> // filter by requester email24// page: <page_state> // Custom State integer, starts at 125// per_page: 20Pro tip: Freshdesk's ticket search uses a specific query syntax with single quotes around string values (`requester:'email@example.com'`). In Bubble, build this query string by concatenating: `"requester:'" + Current User's email + "'"` — make sure the outer double quotes are part of the query parameter value as Freshdesk's docs specify.
Expected result: The customer portal shows a Repeating Group of tickets belonging to the logged-in user. Status badges are color-coded. The customer can filter by status and load more tickets via pagination controls.
Set up inbound Freshdesk webhook events via Bubble Backend Workflow
To receive real-time events from Freshdesk (ticket created, ticket updated, status changed), you expose a Backend Workflow endpoint in Bubble and register it as a webhook target in Freshdesk's Automation rules. This requires a **Bubble paid plan** — Backend Workflows are not available on the Free plan. In Bubble, go to Settings → API and check 'This app exposes a Workflow API.' Your API base URL will be: `https://yourappname.bubbleapps.io/api/1.1/wf/` Open the Backend Workflows section and click 'Add workflow.' Name it `freshdesk_webhook`. In the workflow, click 'Detect data' — Bubble will listen for the next incoming POST request and auto-detect the schema. Meanwhile, in Freshdesk Admin Settings → Automations → Ticket Automations, create a new automation rule. Set the trigger (e.g., 'When a ticket is created' or 'When status changes to Resolved'). Under 'Perform these actions,' choose 'Trigger Webhook.' Enter your Bubble Backend Workflow URL: `https://yourappname.bubbleapps.io/api/1.1/wf/freshdesk_webhook`. Set the webhook method to POST, Content-Type to JSON, and add the ticket fields you want to receive in the body: `ticket_id`, `status`, `subject`, `requester_email`, `priority`. Send a test event from Freshdesk (create a test ticket or trigger the automation manually). Bubble's 'Detect data' captures the incoming JSON and auto-detects the schema fields. After detection, add workflow actions to process the event — for example, updating a Bubble 'Support_Ticket' data type record, sending a Slack notification, or updating the user's ticket count. Add a 'Return data from API' action that responds with `{"status": "ok"}` to acknowledge receipt. Freshdesk expects a 200 response within a few seconds.
1// Bubble Backend Workflow endpoint URL:2https://yourappname.bubbleapps.io/api/1.1/wf/freshdesk_webhook34// Freshdesk webhook payload (example for ticket created event):5// POST to your Backend Workflow URL6// Content-Type: application/json7// Body:8{9 "ticket_id": 12345,10 "subject": "My order hasn't arrived",11 "status": 2,12 "priority": 2,13 "requester_email": "customer@example.com",14 "created_at": "2026-07-10T09:00:00Z"15}1617// Backend Workflow response (to acknowledge receipt):18{ "status": "ok" }1920// Freshdesk Automation setup path:21// Admin Settings → Automations → Ticket Automations22// → New Rule → Trigger: On ticket creation23// → Action: Trigger Webhook → POST to Bubble URLPro tip: Freshdesk's automation webhook retry behavior: if your Backend Workflow does not respond within the timeout period, Freshdesk may retry the webhook. For complex processing (creating multiple records, sending emails), immediately return 200 from the Backend Workflow and queue the heavy work as a separate scheduled Backend Workflow triggered from within the first one. RapidDev's team has built inbound webhook handlers in Bubble for dozens of integrations — for complex automation logic, reach out at rapidevelopers.com/contact.
Expected result: Freshdesk sends a webhook POST to your Bubble Backend Workflow URL when the automation rule triggers. Bubble detects the ticket fields and processes them in the workflow. Bubble Logs tab → Workflow logs shows the incoming request and the 200 acknowledgment response.
Test, secure, and monitor your Freshdesk integration
Before going live, run through this validation checklist: **1. Privacy check:** Open your deployed Bubble app in a browser, open DevTools (F12 → Network tab), and trigger a ticket-loading workflow. Filter network requests by `freshdesk.com`. You should see NO requests going directly to Freshdesk from the browser — all calls should go through Bubble's server. If you see direct browser requests, your API calls are not running server-side. Move them to Backend Workflows and ensure the Private checkbox is set on the Authorization header. **2. Rate limit awareness:** Freshdesk's free and Growth plans allow 40 requests per minute. If multiple users load your portal simultaneously and each triggers a ticket-list API call, you can easily hit this limit. Implement 'lazy loading' — only load tickets when a user explicitly clicks 'Load Tickets' rather than automatically on page load. Pro and Enterprise plans allow 100 req/min. **3. Privacy rules for stored ticket data:** If you cache Freshdesk ticket data in Bubble's database (a common pattern to reduce API calls), add Data tab → Privacy rules to the Ticket data type. Ensure customers can only read their own tickets by setting a rule: 'This thing is visible to Current User when Email = requester_email.' **4. Error handling:** Add workflow error handlers for 401 (check API key encoding), 403 (check account plan limits), 404 (ticket not found — it may have been deleted in Freshdesk), and 429 (rate limit exceeded — add a delay and retry). Display user-friendly messages rather than raw HTTP status codes. **5. Test the complete create-to-display flow:** Create a ticket through your Bubble form, verify it appears in Freshdesk's agent dashboard, then refresh your Bubble portal list to confirm the ticket appears for the customer.
1// Freshdesk API error codes:2// 401 Unauthorized — check base64 encoding; ensure 'X' is uppercase3// 403 Forbidden — check account plan; some endpoints require higher plans4// 404 Not Found — ticket was deleted in Freshdesk or wrong ID5// 429 Too Many Requests — rate limit exceeded (40 req/min on free/Growth)6//7// Privacy rule pattern for Bubble (Data tab → Ticket type → Privacy):8// Rule: 'This Thing is visible to user when9// Ticket's requester_email = Current User's email'10//11// WU economy tip:12// Each Freshdesk API call from Bubble consumes Bubble Workload Units.13// Cache ticket list results in Custom States and refresh only on14// explicit user action — not automatically on every page render.Pro tip: Use Bubble's Logs tab → Workflow logs to inspect every Freshdesk API call during testing. Each log entry shows the request parameters, response status code, and response body — invaluable for debugging 400 errors caused by malformed JSON bodies (especially the description_html vs description field distinction).
Expected result: Your Freshdesk integration is secure (no credentials in browser network tab), handles rate limits gracefully, applies privacy rules to stored ticket data, and shows appropriate error messages to users when Freshdesk calls fail.
Common use cases
White-labeled customer support ticket portal
Build a Bubble portal where your customers log in with their own accounts, see only their support tickets, submit new requests, and check status updates — all styled to match your brand. Freshdesk handles the ticketing, SLA, and agent workflow behind the scenes while your customers interact only with the Bubble UI.
On the Support page, load tickets filtered by the logged-in user's email: GET /tickets with filter=open and requester_id matching the current user's Freshdesk contact ID. Display results in a Repeating Group with ticket subject, status badge, and created date. Add a New Ticket button that opens a form calling POST /tickets on submit.
Copy this prompt to try it in Bubble
Internal ticket triage dashboard for support managers
Create a Bubble dashboard for support managers to view all open and pending tickets, assign them to agents, and update priorities — without agents needing individual Freshdesk logins. This works well for teams using Bubble as their primary operations hub and wanting support visibility in the same interface.
Load all open tickets via GET /tickets?filter=open with pagination. Display in a Bubble table with columns for subject, requester name, priority (color-coded), and assignee. Add a Dropdown to each row to change priority via PATCH /tickets/{id} with the new priority value.
Copy this prompt to try it in Bubble
Automated ticket creation from Bubble form submissions
Trigger Freshdesk ticket creation automatically when a Bubble form is submitted — a contact form, a refund request, or a bug report. The Backend Workflow creates the ticket in Freshdesk with the appropriate tags and priority, and emails a confirmation to the customer. No agent intervention required for intake.
When a Bug Report form is submitted, trigger a Backend Workflow that POSTs to /tickets with subject from the form title field, description_html from the details field, email from the current user's email, priority=2 (medium), and status=2 (open). Store the returned ticket ID in the user's Bubble record for future status lookups.
Copy this prompt to try it in Bubble
Troubleshooting
All Freshdesk API calls return 401 Unauthorized
Cause: The base64-encoded Authorization header is incorrect. The most common mistakes are: encoding `api_key:x` with lowercase x instead of uppercase X, missing the colon between the key and X, missing the space after 'Basic ' in the header value, or the API key has been regenerated in Freshdesk and the header not updated.
Solution: Go to Freshdesk Profile Settings and copy your current API key. Re-encode `your_api_key:X` (uppercase X, colon separator) using `btoa('your_api_key:X')` in a browser console. Update the Authorization header in Bubble's API Connector to `Basic ` + the new base64 string (with a space between 'Basic' and the encoded string). Click Initialize call to verify the new header returns 200.
1// Correct Authorization header format:2// Authorization: Basic WW91ckFwaUtleTEyMzpY3//4// Where 'WW91ckFwaUtleTEyMzpY' = btoa('YourApiKey123:X')5//6// Wrong formats:7// Authorization: Basic WW91ckFwaUtleTEyMzp4 // lowercase x8// Authorization: BasicWW91ckFwaUtleTEyMzpY // missing space after Basic9// Authorization: WW91ckFwaUtleTEyMzpY // missing 'Basic ' prefixPOST /tickets returns 400 Bad Request when creating a ticket
Cause: The most common cause is using the deprecated `description` field instead of `description_html`, or passing an empty required field. Freshdesk v2 requires `description_html` even if the content is plain text. Missing `email` or `subject` also causes 400.
Solution: Check the JSON body of your Create Ticket call in Bubble's API Connector. Replace any `description` field with `description_html` and wrap the value in `<p>...</p>` HTML tags. Verify that `subject`, `email`, `priority`, and `status` are all populated with non-empty values. Use the Initialize call with real test values to confirm the call succeeds before wiring it to a form.
1// Wrong (returns 400):2{ "description": "My order is missing" }34// Correct:5{ "description_html": "<p>My order is missing</p>" }Initialize call fails with 'There was an issue setting up your call'
Cause: Bubble cannot detect the response schema because the Initialize call returned a non-200 response. This happens when the API key is wrong (returns 401) or the base URL has the wrong subdomain (returns 404).
Solution: Verify your Freshdesk subdomain: the URL when logged in to Freshdesk is `https://yourcompany.freshdesk.com` — use `yourcompany` (not `yourcompany.freshdesk.com`) as the subdomain portion of your base URL. The full base URL should be `https://yourcompany.freshdesk.com/api/v2`. After fixing, re-run Initialize call — Bubble needs a valid 200 response to detect the response schema.
Freshdesk ticket list shows all tickets, not just the current user's tickets
Cause: The GET /tickets endpoint without an email filter returns all tickets visible to the API key (typically all tickets in the account). To show only a specific customer's tickets, you must filter by requester email using the search endpoint or the `email` parameter.
Solution: Use GET /search/tickets with query parameter `query` set to `"requester:'user@example.com'"` — building the query string dynamically in Bubble: `"requester:'" + Current User's email + "'"`. Alternatively, use the GET /tickets endpoint with the `email` parameter set to the current user's email address to filter by requester.
1// Correct: filter by requester email2// GET /search/tickets?query="requester:'user@example.com'"3// OR4// GET /tickets?email=user@example.comBackend Workflow for inbound Freshdesk webhooks is not receiving events
Cause: The Backend Workflow API is not enabled in Bubble Settings (Settings → API → 'This app exposes a Workflow API' unchecked), or the workflow URL in Freshdesk's automation includes `/initialize` at the end (the initialization URL variant used during setup — it must be removed for production).
Solution: Go to Bubble Settings → API and verify 'This app exposes a Workflow API' is checked. Check the webhook URL registered in Freshdesk — it should be `https://yourapp.bubbleapps.io/api/1.1/wf/freshdesk_webhook` without `/initialize`. Also confirm the Freshdesk automation rule is active (enabled) and the trigger conditions are met. Check Bubble Logs → Workflow logs for any incoming requests.
Best practices
- Always mark the Authorization header Private in Bubble's API Connector — the base64-encoded string gives full API access to your Freshdesk account and must never appear in browser DevTools.
- Use `description_html` (not the deprecated `description`) for all ticket creation and update calls in Freshdesk v2 — plain `description` is no longer supported and causes 400 errors.
- Cache the ticket list in a Custom State after the first load and show a manual 'Refresh' button rather than auto-refreshing — Freshdesk's 40 req/min limit on free and Growth plans is easily breached by automatic polling in multi-user apps.
- Add Bubble Privacy rules to any Freshdesk ticket data you store in Bubble's database — ensure customers can only read their own tickets by filtering on the requester email field.
- Use Freshdesk's inbound webhook path (Backend Workflow endpoint) for real-time ticket updates instead of polling — webhooks consume fewer API calls and provide instant notification, whereas polling on a timer multiplies API calls with every active user.
- Store the Freshdesk subdomain in a Bubble 'Option Set' or app-level config value rather than hardcoding it in the API Connector base URL — this makes it easier to switch accounts or support multi-tenant setups without re-configuring every API call.
- Handle Freshdesk's numeric status codes with a Bubble Option Set (Open=2, Pending=3, Resolved=4, Closed=5) to display friendly labels and conditional colors rather than raw numbers in your UI.
Alternatives
Zendesk uses the same Basic Auth approach in Bubble but with `email/token:api_token` format (append `/token` to the email) instead of Freshdesk's `api_key:X` pattern. Zendesk is priced higher (Suite Team from $55/agent/mo vs Freshdesk Growth from $15/agent/mo) but has a broader ecosystem and more advanced automation features. Choose Zendesk for enterprise-scale support operations.
Intercom focuses on live chat and customer messaging alongside ticketing, using Bearer token auth in Bubble. Intercom is better suited for proactive customer engagement (in-app chat, product tours) while Freshdesk focuses on structured ticketing workflows. Choose Freshdesk if your team works primarily in ticket queues rather than live chat conversations.
Slack is not a ticketing platform but is often used as a lightweight internal alert channel for support events. Freshdesk webhooks can POST to a Bubble Backend Workflow that then notifies a Slack channel — combine both integrations for a support portal (Freshdesk) with instant team alerts (Slack). Use this combination when your support team monitors tickets from Slack rather than the Freshdesk dashboard.
Frequently asked questions
Why is the password literally the letter X for Freshdesk API authentication?
Freshdesk uses standard HTTP Basic Auth, which requires both a username and password. When using an API key instead of a user's actual password, Freshdesk's documentation specifies using the API key as the username and any non-empty string — conventionally the letter X — as the placeholder password. The X has no special meaning; it just satisfies the Basic Auth format requirement. Freshdesk ignores the password value and authenticates solely based on the API key.
Do I need a paid Freshdesk plan to use the API in Bubble?
Freshdesk's API is available on the free 3-agent Sprout plan and all paid plans. However, the free plan has lower rate limits (40 requests per minute) and limited webhook trigger options in Automation rules. For a production Bubble customer portal, a paid Freshdesk plan (Growth or above) is recommended for higher rate limits (100 req/min on Pro+) and more automation flexibility. Inbound webhooks to Bubble also require a Bubble paid plan for Backend Workflows.
How do I let customers view only their own tickets in my Bubble portal?
Use the Freshdesk search endpoint — GET /search/tickets with the query parameter set to `"requester:'customer@example.com'"` (with the customer's email from Bubble's Current User object). This returns only tickets where that email is the requester. Do not use GET /tickets without filtering — it returns all tickets visible to your API key, which would expose other customers' data.
Can I allow customers to add replies or comments to their Freshdesk tickets from Bubble?
Yes. Freshdesk's API supports adding conversation notes via POST /tickets/{id}/reply with a body containing `body_html` (the reply text as HTML) and the `user_id` of the replying agent, or `from_email` for customer replies. Add a Reply text area and Send button on your ticket detail page, wiring the submit action to a Backend Workflow that calls this endpoint.
What should I do when the Freshdesk rate limit is exceeded?
Freshdesk returns HTTP 429 when the rate limit (40 req/min on free/Growth, 100 req/min on Pro+) is breached. In Bubble's workflow error handler, add a 'When API call returns error 429' condition that shows a user-friendly message like 'Loading is temporarily paused — please try again in a moment' instead of a raw error. Avoid automatic polling intervals; use manual 'Refresh' buttons to give users control over when API calls fire.
How do I update a ticket's status from my Bubble portal — for example, mark it as Resolved?
Use PATCH /tickets/{id} with a body containing `{"status": 4}` (4=Resolved). Add a 'Mark Resolved' button in your ticket detail view that triggers a Backend Workflow calling this PATCH action. After the call succeeds, update the status displayed in your Bubble UI using a Custom State change so the customer sees the updated status immediately without reloading the full ticket list.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation