# Create Agent Source: https://docs.libredesk.io/api-reference/endpoint/agents/create-agent post /api/v1/agents Requires the `users:manage` permission. # Delete Agent Source: https://docs.libredesk.io/api-reference/endpoint/agents/delete-agent delete /api/v1/agents/{id} Requires the `users:manage` permission. # Generate API Key Source: https://docs.libredesk.io/api-reference/endpoint/agents/generate-a-p-i-key post /api/v1/agents/{id}/api-key Requires the `users:manage` permission. # Get Agent Source: https://docs.libredesk.io/api-reference/endpoint/agents/get-agent get /api/v1/agents/{id} Requires the `users:manage` permission. # Get Agents Source: https://docs.libredesk.io/api-reference/endpoint/agents/get-agents get /api/v1/agents Requires the `users:manage` permission. # Get Current Agent Source: https://docs.libredesk.io/api-reference/endpoint/agents/get-current-agent get /api/v1/agents/me # Revoke API Key Source: https://docs.libredesk.io/api-reference/endpoint/agents/revoke-a-p-i-key delete /api/v1/agents/{id}/api-key Requires the `users:manage` permission. # Update Agent Source: https://docs.libredesk.io/api-reference/endpoint/agents/update-agent put /api/v1/agents/{id} Requires the `users:manage` permission. # Update Current Agent Availability Source: https://docs.libredesk.io/api-reference/endpoint/agents/update-agent-availability put /api/v1/agents/me/availability # AI Completion Source: https://docs.libredesk.io/api-reference/endpoint/ai-completions/a-i-completion post /api/v1/ai/completion # Get AI Prompts Source: https://docs.libredesk.io/api-reference/endpoint/ai-completions/get-a-i-prompts get /api/v1/ai/prompts # Create Contact Note Source: https://docs.libredesk.io/api-reference/endpoint/contact-notes/create-contact-note post /api/v1/contacts/{id}/notes Requires the `contact_notes:write` permission. # Delete Contact Note Source: https://docs.libredesk.io/api-reference/endpoint/contact-notes/delete-contact-note delete /api/v1/contacts/{id}/notes/{note_id} Deletes a contact note. Agents without the Admin role can delete only their own notes. Requires the `contact_notes:delete` permission. # Get Contact Notes Source: https://docs.libredesk.io/api-reference/endpoint/contact-notes/get-contact-notes get /api/v1/contacts/{id}/notes Requires the `contact_notes:read` permission. # Block or Unblock Contact Source: https://docs.libredesk.io/api-reference/endpoint/contacts/block-contact put /api/v1/contacts/{id}/block Requires the `contacts:block` permission. # Delete Contact Source: https://docs.libredesk.io/api-reference/endpoint/contacts/delete-contact delete /api/v1/contacts/{id} Permanently deletes the contact along with their conversations, messages and notes. Requires the `contacts:delete` permission. # Export Contact Data Source: https://docs.libredesk.io/api-reference/endpoint/contacts/export-contact-data get /api/v1/contacts/{id}/export Requires the `contacts:export` permission. # Get Contact Source: https://docs.libredesk.io/api-reference/endpoint/contacts/get-contact get /api/v1/contacts/{id} Requires the `contacts:read` permission. # Get Contacts Source: https://docs.libredesk.io/api-reference/endpoint/contacts/get-contacts get /api/v1/contacts Requires the `contacts:read_all` permission. # Update Contact Source: https://docs.libredesk.io/api-reference/endpoint/contacts/update-contact put /api/v1/contacts/{id} Requires the `contacts:write` permission. # Create Conversation Source: https://docs.libredesk.io/api-reference/endpoint/conversations/create-conversation post /api/v1/conversations Requires the `conversations:write` permission. # Download Conversation Transcript Source: https://docs.libredesk.io/api-reference/endpoint/conversations/download-conversation-transcript get /api/v1/conversations/{uuid}/transcript Requires the `messages:read` permission. # Get all Conversations Source: https://docs.libredesk.io/api-reference/endpoint/conversations/get-all-conversations get /api/v1/conversations/all Requires the `conversations:read_all` permission. # Get Assigned Conversations Source: https://docs.libredesk.io/api-reference/endpoint/conversations/get-assigned-conversations get /api/v1/conversations/assigned Get all assigned conversations to current user. Requires the `conversations:read_assigned` permission. # Get Conversation Source: https://docs.libredesk.io/api-reference/endpoint/conversations/get-conversation get /api/v1/conversations/{uuid} Requires the `conversations:read` permission. # Get Conversation Participants Source: https://docs.libredesk.io/api-reference/endpoint/conversations/get-conversation-participants get /api/v1/conversations/{uuid}/participants Requires the `conversations:read` permission. # Get Message Source: https://docs.libredesk.io/api-reference/endpoint/conversations/get-message get /api/v1/conversations/{cuuid}/messages/{uuid} Requires the `messages:read` permission. # Get Messages Source: https://docs.libredesk.io/api-reference/endpoint/conversations/get-messages get /api/v1/conversations/{uuid}/messages Requires the `messages:read` permission. # Get Team Unassigned Conversations Source: https://docs.libredesk.io/api-reference/endpoint/conversations/get-team-unassigned-conversations get /api/v1/teams/{id}/conversations/unassigned Get conversation assigned to team but not assigned to any agent. Requires the `conversations:read_team_inbox` permission. # Get Unassigned Conversations Source: https://docs.libredesk.io/api-reference/endpoint/conversations/get-unassigned-conversations get /api/v1/conversations/unassigned Get all conversations not assigned to any user and team. Requires the `conversations:read_unassigned` permission. # Get View Conversations Source: https://docs.libredesk.io/api-reference/endpoint/conversations/get-view-conversations get /api/v1/views/{id}/conversations Get conversations for a specific view. The view's saved filters are applied. Requires the `conversations:read` permission. # Remove Team Assignee Source: https://docs.libredesk.io/api-reference/endpoint/conversations/remove-team-assignee put /api/v1/conversations/{uuid}/assignee/team/remove Requires the `conversations:update_team_assignee` permission. # Remove User Assignee Source: https://docs.libredesk.io/api-reference/endpoint/conversations/remove-user-assignee put /api/v1/conversations/{uuid}/assignee/user/remove Requires the `conversations:update_user_assignee` permission. # Retry Message Source: https://docs.libredesk.io/api-reference/endpoint/conversations/retry-message put /api/v1/conversations/{cuuid}/messages/{uuid}/retry Retries sending a failed message in the specified conversation. Only failed outgoing messages sent by the current agent can be retried. Requires the `messages:write` permission. # Send Message Source: https://docs.libredesk.io/api-reference/endpoint/conversations/send-message post /api/v1/conversations/{cuuid}/messages Requires the `messages:write` permission. # Update Contact Custom Attributes Source: https://docs.libredesk.io/api-reference/endpoint/conversations/update-contact-custom-attributes put /api/v1/conversations/{uuid}/contacts/custom-attributes Updates custom attributes of the contact on the conversation. Returns 500 when the request body is not valid JSON. # Update Conversation Assignee Last Seen Source: https://docs.libredesk.io/api-reference/endpoint/conversations/update-conversation-assignee-last-seen put /api/v1/conversations/{uuid}/last-seen Requires the `conversations:read` permission. # Update Conversation Custom Attributes Source: https://docs.libredesk.io/api-reference/endpoint/conversations/update-conversation-custom-attributes put /api/v1/conversations/{uuid}/custom-attributes Returns 500 when the request body is not valid JSON. # Update Conversation Priority Source: https://docs.libredesk.io/api-reference/endpoint/conversations/update-conversation-priority put /api/v1/conversations/{uuid}/priority Requires the `conversations:update_priority` permission. # Update Conversation Status Source: https://docs.libredesk.io/api-reference/endpoint/conversations/update-conversation-status put /api/v1/conversations/{uuid}/status Requires the `conversations:update_status` permission. # Update conversation tags Source: https://docs.libredesk.io/api-reference/endpoint/conversations/update-conversationtags post /api/v1/conversations/{uuid}/tags Requires the `conversations:update_tags` permission. # Update Team Assignee Source: https://docs.libredesk.io/api-reference/endpoint/conversations/update-team-assignee put /api/v1/conversations/{uuid}/assignee/team Requires the `conversations:update_team_assignee` permission. # Update User Assignee Source: https://docs.libredesk.io/api-reference/endpoint/conversations/update-user-assignee put /api/v1/conversations/{uuid}/assignee/user Requires the `conversations:update_user_assignee` permission. # List Custom Attributes Source: https://docs.libredesk.io/api-reference/endpoint/custom-attributes/get-custom-attributes get /api/v1/custom-attributes # Health Check Source: https://docs.libredesk.io/api-reference/endpoint/health-check/health-check get /health # Get Public Help Center Article Source: https://docs.libredesk.io/api-reference/endpoint/help-center/get-public-help-center-article get /api/v1/public/help-centers/{slug}/articles/{article_slug} # Get Public Help Center Tree Source: https://docs.libredesk.io/api-reference/endpoint/help-center/get-public-help-center-tree get /api/v1/public/help-centers/{slug}/tree # Search Public Help Center Source: https://docs.libredesk.io/api-reference/endpoint/help-center/search-public-help-center get /api/v1/public/help-centers/{slug}/search # List Inboxes Source: https://docs.libredesk.io/api-reference/endpoint/inboxes/get-inboxes get /api/v1/inboxes # Media Upload Source: https://docs.libredesk.io/api-reference/endpoint/media/media-upload post /api/v1/media # Serve Media Source: https://docs.libredesk.io/api-reference/endpoint/media/serve-media get /uploads/{uuid} Public media is served without authentication. Private media requires authentication with access to the linked record, or a valid signed URL. # Get Overview Charts Source: https://docs.libredesk.io/api-reference/endpoint/reports/overview-charts get /api/v1/reports/overview/charts Requires the `reports:manage` permission. # Get Overview Counts Source: https://docs.libredesk.io/api-reference/endpoint/reports/overview-counts get /api/v1/reports/overview/counts Requires the `reports:manage` permission. # Get Overview CSAT Source: https://docs.libredesk.io/api-reference/endpoint/reports/overview-csat get /api/v1/reports/overview/csat Requires the `reports:manage` permission. # Get Overview Message Volume Source: https://docs.libredesk.io/api-reference/endpoint/reports/overview-message-volume get /api/v1/reports/overview/messages Requires the `reports:manage` permission. # Get Overview SLA Source: https://docs.libredesk.io/api-reference/endpoint/reports/overview-s-l-a get /api/v1/reports/overview/sla Requires the `reports:manage` permission. # Get Overview Tag Distribution Source: https://docs.libredesk.io/api-reference/endpoint/reports/overview-tag-distribution get /api/v1/reports/overview/tags Requires the `reports:manage` permission. # Search Contacts Source: https://docs.libredesk.io/api-reference/endpoint/search/search-contacts get /api/v1/contacts/search Requires the `contacts:read` permission. # Search Conversations Source: https://docs.libredesk.io/api-reference/endpoint/search/search-conversations get /api/v1/conversations/search Requires the `conversations:read` permission. # Search Messages Source: https://docs.libredesk.io/api-reference/endpoint/search/search-messages get /api/v1/messages/search Requires the `messages:read` permission. # Create Conversation Status Source: https://docs.libredesk.io/api-reference/endpoint/status-and-priority/create-status post /api/v1/statuses Requires the `status:manage` permission. # Delete Conversation Status Source: https://docs.libredesk.io/api-reference/endpoint/status-and-priority/delete-status delete /api/v1/statuses/{id} Default statuses (Open, Snoozed, Resolved, Closed) and statuses in use cannot be deleted. Requires the `status:manage` permission. # Get Conversation Priorities Source: https://docs.libredesk.io/api-reference/endpoint/status-and-priority/get-priorities get /api/v1/priorities # Get Conversation Statuses Source: https://docs.libredesk.io/api-reference/endpoint/status-and-priority/get-statuses get /api/v1/statuses # Update Conversation Status Source: https://docs.libredesk.io/api-reference/endpoint/status-and-priority/update-status put /api/v1/statuses/{id} Default statuses (Open, Snoozed, Resolved, Closed) cannot be updated. Requires the `status:manage` permission. # Create Tag Source: https://docs.libredesk.io/api-reference/endpoint/tags/create-tag post /api/v1/tags Requires the `tags:manage` permission. # Delete Tag Source: https://docs.libredesk.io/api-reference/endpoint/tags/delete-tag delete /api/v1/tags/{id} Requires the `tags:manage` permission. # List Tags Source: https://docs.libredesk.io/api-reference/endpoint/tags/get-tags get /api/v1/tags # Update Tag Source: https://docs.libredesk.io/api-reference/endpoint/tags/update-tag put /api/v1/tags/{id} Requires the `tags:manage` permission. # Create Team Source: https://docs.libredesk.io/api-reference/endpoint/teams/create-team post /api/v1/teams Requires the `teams:manage` permission. # Delete Team Source: https://docs.libredesk.io/api-reference/endpoint/teams/delete-team delete /api/v1/teams/{id} Requires the `teams:manage` permission. # Get Team Source: https://docs.libredesk.io/api-reference/endpoint/teams/get-team get /api/v1/teams/{id} Requires the `teams:manage` permission. # Get Teams Source: https://docs.libredesk.io/api-reference/endpoint/teams/get-teams get /api/v1/teams Requires the `teams:manage` permission. # Update Team Source: https://docs.libredesk.io/api-reference/endpoint/teams/update-team put /api/v1/teams/{id} Requires the `teams:manage` permission. # API Introduction Source: https://docs.libredesk.io/api-reference/introduction Getting started with the Libredesk API You can access the Libredesk API to interact with your instance programmatically by generating API keys for any agent. Some endpoints are not documented yet. Pull requests to the docs are welcome. ## Authentication The API supports two authentication methods: ### Basic authentication ```bash theme={null} curl -X GET "https://your-instance.com/api/v1/endpoint" \ -H "Authorization: Basic " ``` ### Token authentication ```bash theme={null} curl -X GET "https://your-instance.com/api/v1/endpoint" \ -H "Authorization: token api_key:api_secret" ``` ## Getting API keys 1. Go to **Admin → Teammate → Agent → Edit** 2. Generate a new API key 3. Save both the API Key and API Secret ## Base URL ``` https://your-instance.com/api/v1 ``` ## Response format Every response is JSON. # Widget API Source: https://docs.libredesk.io/api-reference/widget-api Build custom live chat widget frontends with Libredesk's public widget endpoints and WebSocket protocol This document summarizes the public endpoints and WebSocket messages used by the Libredesk live chat widget. These APIs are intended for custom widget frontends that talk to an existing Libredesk instance. ## Authentication and inbox selection Widget HTTP requests identify the live chat inbox with either: * `X-Libredesk-Inbox-ID: ` header * `?inbox_id=` query parameter Authenticated widget requests also send: * `Authorization: Bearer ` When a verified contact is replacing a previous visitor session, the widget can also send: * `X-Libredesk-Visitor-Token: ` If Libredesk merges the visitor into the verified contact, the response includes `X-Libredesk-Clear-Visitor: true` so the custom frontend can discard the visitor token. API responses use Libredesk's standard envelope format. Successful responses put the endpoint payload in `data`. ## Public settings endpoints ### `GET /api/v1/widget/chat/settings/launcher` Returns launcher-only settings for the embeddable script before the widget iframe is opened. The inbox can be passed as `?inbox_id=`. ### `GET /api/v1/widget/chat/settings` Returns the live chat widget settings, including public configuration and, when enabled, business hours and pre-chat custom attribute metadata. ## Session endpoints ### `POST /api/v1/widget/chat/auth/exchange` Exchanges a customer-generated JWT for a widget session token. Request body: ```json theme={null} { "jwt": "" } ``` The JWT must include `external_user_id`, `email`, and `first_name`. It can also include `last_name`, `phone_number`, `phone_number_country_code` (ISO 3166-1 alpha-2, e.g. `IN`), and `contact_custom_attributes`. Response `data` includes: ```json theme={null} { "session_token": "", "user": { "user_id": 123, "is_visitor": false, "first_name": "Ada", "last_name": "Lovelace" } } ``` ### `GET /api/v1/widget/chat/auth/me` Returns the current widget user's metadata for the bearer session token. ## Conversation endpoints ### `POST /api/v1/widget/chat/conversations/init` Starts a new live chat conversation. If no bearer token is present, Libredesk creates a visitor and returns a new session token. Request body: ```json theme={null} { "message": "Hello, I need help", "form_data": { "company": "Example Co" } } ``` Response `data` includes the created `conversation`, `messages`, optional business hours fields, and, for a new visitor, `session_token` plus `user`. ### `GET /api/v1/widget/chat/conversations` Returns the conversations visible to the current widget user for the selected inbox. ### `GET /api/v1/widget/chat/conversations/{uuid}` Returns one conversation with its messages and optional business hours metadata. ### `POST /api/v1/widget/chat/conversations/{uuid}/message` Sends a text message to an existing conversation. Request body: ```json theme={null} { "message": "Here are more details" } ``` ### `POST /api/v1/widget/chat/conversations/{uuid}/update-last-seen` Marks the conversation as seen by the widget user. ## Upload endpoint ### `POST /api/v1/widget/media/upload` Uploads one or more files to an existing conversation. This endpoint requires `multipart/form-data`. Form fields: * `conversation_uuid`: target conversation UUID * `files`: one or more file parts File uploads are rejected when the inbox has file upload disabled, the file is empty, the file is larger than the configured limit, or the extension is not allowed. ## WebSocket endpoint ### `GET /widget/ws` The widget uses this WebSocket endpoint for realtime conversation events. After opening the socket, send a `join` message with the inbox UUID and session token: ```json theme={null} { "type": "join", "token": "", "data": { "inbox_id": "" } } ``` The server replies with: ```json theme={null} { "type": "joined", "data": { "message": "namaste!" } } ``` ### Client-to-server messages `typing` broadcasts the visitor typing state to agents: ```json theme={null} { "type": "typing", "data": { "conversation_uuid": "", "is_typing": true } } ``` `page_visit` stores the visitor's current page and broadcasts recent page visits to agents: ```json theme={null} { "type": "page_visit", "data": { "url": "https://example.com/pricing", "title": "Pricing" } } ``` `ping` keeps the session active and should be sent periodically: ```json theme={null} { "type": "ping" } ``` The server replies with `pong`. ### Server-to-client messages The widget client handles these message types: * `joined`: the socket joined the live chat inbox. * `pong`: response to `ping`. * `new_message`: a new chat message; `data` is a chat message payload. * `typing`: agent typing status with `conversation_uuid` and `is_typing`. * `conversation_update`: partial conversation update. * `error`: socket-level error payload. # AI Source: https://docs.libredesk.io/configuration/ai Connect an AI provider, build a knowledge base, and let an AI assistant answer customers There are two things here. One is a set of helpers for your agents: copilot, drafted replies, conversation summaries, and tag suggestions. The other is AI assistants, which reply to customers on their own, search your knowledge base, call your APIs, and hand the conversation to a human when they cannot help. Both are configured under **Admin → AI**, and neither works until you add a provider. AI is currently in beta. Assistant replies go out to customers, so test on a low-volume inbox first. ## Providers Libredesk talks to any OpenAI-compatible endpoint: OpenAI, Anthropic, OpenRouter, Groq, Together AI, Azure, LiteLLM, or a local Ollama. Pick a preset to pre-fill the base URL and model, then edit anything you want. There are two providers to configure: | Provider | Used for | | -------------- | -------------------------------------------------------------- | | **Completion** | Drafting replies, copilot, summaries, assistant responses | | **Embedding** | Turning snippets into vectors so the assistant can search them | Go to **Admin → AI → Providers**. Pick a preset, paste your API key, and save. Fill in the embedding provider on the same page. **Dimensions** must match the model's output size (1536 for `text-embedding-3-small`, 3072 for `text-embedding-3-large`, 768 for `nomic-embed-text`). Hit **Test connection** on each. Libredesk sends a real request and reports what the provider said, including a dimensions mismatch. ### Completion fields | Field | Notes | | -------------------------- | ----------------------------------------------------------------------------------------------------------------- | | Model | Model ID as the provider names it, e.g. `gpt-5.4-mini`. | | Base URL | Leave blank for the OpenAI default. | | Temperature | 0 to 2. Leave blank to use the model's default. Some models reject any other value. | | Max tokens | Cap on the reply length. | | Reasoning effort | `none`, `low`, `medium`, `high`. Leave blank for most models. | | Custom instructions | Added to every AI prompt. Good place for tone and format rules. | | Model supports image input | Off by default. When on, image attachments from customers are sent to the model. When off, images are never sent. | Changing the embedding model or dimensions re-embeds existing snippets automatically. Model choice matters. The cheapest models ignore your instructions and guardrails and make things up. You do not need a frontier model either. A mid-tier model like Claude Haiku or GPT-5.4 Mini is the right balance for support replies. Move up a tier if you see replies drifting from your instructions. Every AI feature calls your provider and costs money on your account. Max tokens, max turns, and max steps are the settings that cap how much a single conversation can spend. ## Snippets Snippets are the knowledge base. The assistant and copilot search them to answer questions. One library, shared by every assistant. Write each snippet as a self-contained answer, not a fragment. A snippet titled "Refund policy" with the full policy in the body works; a single line pulled out of a longer page does not. You can add snippets three ways: * **Manually** - **New snippet**, title and content. * **Import from URL** - Libredesk fetches the page, keeps the readable content, and adds it as a snippet you can edit afterwards. * **Suggestions** - mined from resolved conversations, see below. ### Learn from resolved conversations Turn on **Learn from resolved conversations** under **Admin → AI → Suggestions**. Resolved conversations where a human agent gave a reusable answer are mined into FAQ suggestions. Each suggestion shows the question, the drafted answer, and a link to the conversation it came from. Approving one adds it to snippets. Rejecting one drops it. Nothing reaches the assistant until you approve it. ## Assistants An assistant is a persona with its own name, avatar, tone, instructions, and set of tools. Create as many as you need: one for billing, one for order status. Assistants show up alongside your agents as assignees. **A conversation gets an AI reply only when it is assigned to an assistant**, so route work to one with an automation rule (or assign it by hand to try it out). The assistant replies on both email and live chat, and only on open conversations - never snoozed or resolved ones. ### Settings | Field | What it does | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Name, avatar | What customers see on the reply. | | Description | Your own note. Customers never see it. | | Expectation message | Shown to the customer while the assistant is handling the chat, e.g. "I'm an AI assistant here to help with your order." Disappears on handoff. | | Tone, response length | Friendly, professional, neutral, casual. Concise, balanced, detailed. | | Max turns | Replies before it hands off. 1 to 20. | | Languages | Languages it may reply in. It matches the customer when their language is on the list, otherwise it uses the first one. Leave empty to always match the customer. | | Offer handoff to a human | When off, it never offers a human and says it cannot help instead. | | Fallback team | Team the conversation lands in on handoff. | | Instructions | How it should respond. "Always confirm the order ID before looking anything up." | | Guardrails | Rules it must never break. "Never issue a refund without a human agent's approval." | | Tools | Tools this assistant may call. It can only use the ones you grant. | ### Handoff The assistant hands the conversation to a human, unassigns itself, and posts an activity message when it: * decides it cannot help * hits its max turns * errors out or times out * sees someone other than the contact reply on the thread ### Test and Performance The assistant edit page has two extra tabs: * **Test** - send a sample customer message and see the reply it would draft, plus which snippets it used. Nothing is saved or sent. * **Performance** - conversations, replies, resolution rate, handoff rate, reopen rate, replies per conversation, and CSAT for that assistant. ## Tools Tools let an assistant call your own APIs while replying, to look up an order, check a subscription, or cancel something. Create them under **Admin → AI → Tools**, then grant them per assistant. | Field | Notes | | ------------------------ | ------------------------------------------------------------------------------------------------------- | | Name | The function name the model calls. Letters, numbers, hyphen, underscore, up to 64 characters. | | Description | The model reads this to decide when to call the tool. Be precise about what it does and when to use it. | | URL, method | `GET` or `POST`. | | Headers | Sent with every request. Values are encrypted at rest. | | Parameters | JSON schema for the arguments the model may pass. Leave blank for a single text input. | | Require verified contact | On by default. See below. | ```json Parameters example theme={null} { "type": "object", "properties": { "order_id": { "type": "string", "description": "The order ID to look up" } }, "required": ["order_id"] } ``` On `GET` the arguments go on the query string. On `POST` they are the JSON body. ### Identity headers Libredesk adds these headers to every tool request, server-side. The model never sees them and cannot change them. | Header | Value | | --------------------------------- | ---------------------------------------------------------------------------------------- | | `X-Libredesk-Contact-Id` | Contact ID in Libredesk. | | `X-Libredesk-Contact-Email` | Current email on the contact. | | `X-Libredesk-Contact-External-Id` | Your own user ID, if the contact came in through a verified live chat session. | | `X-Libredesk-Contact-Type` | `contact` or `visitor`. | | `X-Libredesk-Contact-Verified` | `true` or `false`, see below. Always sent. Read this before returning any personal data. | | `X-Libredesk-Conversation-UUID` | Conversation the tool call belongs to. | | `X-Libredesk-Inbox-Id` | Inbox the conversation arrived on. | ### Require verified contact An email address a customer types into a chat proves nothing. With **Require verified contact** on, the assistant emails a one-time code to the contact and asks them to paste it back before the tool can run. Codes expire in 10 minutes and both sends and attempts are capped. Turn it off only if your endpoint authenticates the customer itself. Once it is off, anyone who can reach the chat widget can trigger that tool for that contact. **Check `X-Libredesk-Contact-Verified` in your endpoint on every call. Return personal data, or act on the customer's account, only when it is `true`.** The toggle above is a gate inside Libredesk, in front of the model. It is not a guarantee your endpoint gets: with the toggle off, or on a tool where it was never turned on, requests reach you with the header set to `false`. The header is the only thing that actually proves the customer owns the email they claimed, so treat it as the source of truth and check it yourself. Do not rely on the assistant having asked for a code first. Tool URLs are fetched by your Libredesk server. If you accept tool definitions from people you do not fully trust, turn on [SSRF protection](/configuration/ssrf-protection). ### Writing a custom tool The tool config above is the easy half. The endpoint it calls is where the work is, and three rules cover most of it: 1. **Check your own secret first.** Configure a header like `X-Api-Key` on the tool and reject any request that does not carry it. Your endpoint is on the internet; the tool config is the only thing that proves the call came from Libredesk. 2. **Take the customer's identity from the headers, never from the arguments.** Libredesk injects the headers server-side and the model cannot see or change them. Anything in the request body was written by the model, from what the customer typed, so treat it as untrusted input. If the model asks for `account_id=99999`, look up the account that belongs to the contact in the header and ignore the number. 3. **Gate sensitive data on `X-Libredesk-Contact-Verified`.** A visitor can claim any email address in chat. That header is `true` only after the contact proved the address with a one-time code, so require it before returning anything you would not show a stranger, and before anything that changes their account: a cancellation, a refund, an address update. ```go theme={null} const apiKey = "your-shared-secret" func handleBalance(w http.ResponseWriter, r *http.Request) { if r.Header.Get("X-Api-Key") != apiKey { http.Error(w, "unauthorized", http.StatusUnauthorized) return } if r.Header.Get("X-Libredesk-Contact-Verified") != "true" { http.Error(w, "contact not verified", http.StatusForbidden) return } // Identity comes from the header. Nothing in the body decides whose data this is. email := r.Header.Get("X-Libredesk-Contact-Email") account, ok := lookupByEmail(email) if !ok { http.Error(w, "no account for this contact", http.StatusNotFound) return } json.NewEncoder(w).Encode(map[string]any{ "balance": account.Balance, "currency": "INR", }) } ``` Return small, flat JSON. The response goes into the model's context and long payloads are truncated, so send the three fields the assistant needs to answer rather than your whole account object. **Use the status code to say whether the request worked, not whether it found anything.** A 2xx response reads as data, so the assistant answers from it. A non-2xx response reads as a failure, and the assistant usually tells the customer it could not help and hands the conversation to a human. So "this customer has no orders" is a 2xx with an empty list or a short message. Keep non-2xx for requests that genuinely could not be served: a bad API key, a missing identifier, your upstream being down. The body is handed back to the model as text either way, so error messages are worth writing properly. "No order found for that ID" lets the assistant ask the customer to check the number. A bare `500` usually ends in a handoff. The repo ships a runnable version of this at [`scripts/tools-server`](https://github.com/abhinavxd/libredesk/tree/main/scripts/tools-server). Run it with `go run ./scripts/tools-server`, point a tool at `http://localhost:7070/account`, and it logs every header and argument it receives. ## For your agents These need only the completion provider, and work whether or not you use assistants. * **Copilot** - a panel in the conversation sidebar. Ask it anything about the conversation; it searches your snippets and the contact's history. Insert its answer into the reply box or add it as a private note. Rename it under **Admin → General**. * **Drafted replies** - describe what you want to say in the reply box and let AI write the reply. * **Rewrite prompts** - fix grammar and spelling, and the other prompts in the reply box AI menu. * **Summarize conversation** - drops an AI summary into the conversation as a private note. * **Tag suggestions** - suggests tags from your existing tag list. ## Limits Assistant worker and run limits live in `config.toml`: ```toml theme={null} [ai_agent] # Number of workers running autonomous AI assistant responses worker_count = 10 # Maximum number of AI assistant response jobs that can be queued queue_size = 1000 # Maximum number of tool calls the assistant can make while working on one reply. (1-20) max_steps = 6 # Conversation messages sent to the model as history. (5-100) max_history_messages = 30 ``` One assistant run is capped at 90 seconds on live chat and 3 minutes on email, with each provider call capped at 60 seconds. A run that hits a cap hands off to a human. # Connecting Inboxes Source: https://docs.libredesk.io/configuration/connecting-inboxes Connect email inboxes using Google, Microsoft OAuth, or manual IMAP/SMTP configuration Libredesk supports three methods for connecting email inboxes: Google OAuth, Microsoft OAuth, and manual IMAP/SMTP configuration with password authentication. ## Google setup Connect your Gmail or Google Workspace inbox using an app password or OAuth 2.0. ### App password An app password is the quickest way to connect Gmail. It needs no Google Cloud Console setup. Your Google account must have [2-Step Verification](https://myaccount.google.com/signinoptions/twosv) enabled for this option to be available. Visit [myaccount.google.com/apppasswords](https://myaccount.google.com/apppasswords), name the app (e.g. `Libredesk`), and copy the generated 16-character password. Go to Admin → Inboxes → New inbox → **Other provider** and use these settings: **IMAP** | Field | Value | | -------- | ---------------------------- | | Host | `imap.gmail.com` | | Port | `993` | | TLS | SSL/TLS | | Username | Your full Gmail address | | Password | The app password from step 1 | **SMTP** | Field | Value | | ----------------------- | ---------------------------- | | Host | `smtp.gmail.com` | | Port | `587` | | TLS | STARTTLS | | Authentication Protocol | PLAIN | | Username | Your full Gmail address | | Password | The app password from step 1 | Click **Save**. Libredesk does not check the mailbox immediately. It checks on the **Scan interval** set in the same form, which is `5m` by default. So: send a test email to the address, wait 5 minutes, then look in Conversations. ### OAuth #### Prerequisites * A Google Cloud Console project * OAuth 2.0 credentials (Client ID & Client Secret) #### Required OAuth scopes Your Google OAuth app needs these scopes: | Scope | Purpose | | ------------------------------------------------ | ------------------------------------------------ | | `https://mail.google.com/` | Full Gmail access for reading and sending emails | | `https://www.googleapis.com/auth/userinfo.email` | Access to user's email address | #### Configuration steps In Libredesk, go to Admin → Inboxes → New inbox → Google. A dialog opens with the **Callback URL**. Copy it. Go to the [Google Cloud Console](https://console.cloud.google.com/) and create a new project or select an existing one. In your project, navigate to APIs & Services → Library and enable the Gmail API. Set up the OAuth consent screen with the required scopes listed above. Go to APIs & Services → Credentials and click Create Credentials → OAuth client ID: * Application type: Web application * Add the callback URL from step 1 to Authorized redirect URIs * Note down the Client ID and Client Secret Back in the Libredesk dialog, enter the Client ID and Client Secret, then click Authorize. You'll be redirected to Google's consent screen to grant permissions. Click **Save**. Libredesk does not check the mailbox immediately. It checks on the **Scan interval** set in the same form, which is `5m` by default. So: send a test email to the address, wait 5 minutes, then look in Conversations. ## Microsoft setup Connect your Outlook or Microsoft 365 inbox using OAuth 2.0. ### Prerequisites * An Azure Active Directory (Azure AD) app registration * OAuth 2.0 credentials (Client ID & Client Secret) ### Required API permissions Add the following **Microsoft Graph** delegated permissions in Azure Portal: Microsoft API Permissions | Permission | Type | Description | | --------------------- | --------- | --------------------------------------------------- | | email | Delegated | View users' email address | | IMAP.AccessAsUser.All | Delegated | Read and write access to mailboxes via IMAP | | offline\_access | Delegated | Maintain access to data you have given it access to | | openid | Delegated | Sign users in | | SMTP.Send | Delegated | Send emails from mailboxes using SMTP AUTH | | User.Read | Delegated | Sign in and read user profile | ### Configuration steps In Libredesk, go to Admin → Inboxes → New inbox → Microsoft. A dialog opens with the **Callback URL**. Copy it. Go to the [Azure Portal](https://portal.azure.com/) → Azure Active Directory → App registrations → New registration: * Enter a name for your application * Select the appropriate account type (single tenant or multi-tenant) * Add the callback URL from step 1 as the Redirect URI In your app registration, go to API permissions → Add a permission → Microsoft Graph: * Add all the delegated permissions shown in the table above * Click Grant admin consent for Default Directory Go to Certificates & secrets → New client secret: * Add a description and expiration period * Copy the secret value immediately (it won't be shown again) From the Overview page, copy: * Application (client) ID * Directory (tenant) ID (if using single-tenant) Back in the Libredesk dialog, enter the Client ID, Client Secret, and Tenant ID (optional), then click Authorize. You'll be redirected to Microsoft's consent screen to grant permissions. Click **Save**. Libredesk does not check the mailbox immediately. It checks on the **Scan interval** set in the same form, which is `5m` by default. So: send a test email to the address, wait 5 minutes, then look in Conversations. ## Manual IMAP/SMTP setup Use manual IMAP/SMTP for self-hosted mail servers and for providers that don't support OAuth. For Gmail, use the [app password](#app-password) option under Google setup instead. ### Configuration steps Navigate to Admin → Inboxes → New inbox → Other provider, configure IMAP and SMTP settings, then click Save. Libredesk does not check the mailbox immediately. It checks on the **Scan interval** set in the same form, which is `5m` by default. So: send a test email to the address, wait 5 minutes, then look in Conversations. ## Live chat For live chat widget setup, see the [Live Chat](/configuration/livechat) guide. # Context Links Source: https://docs.libredesk.io/configuration/context-links Open external tools from the conversation sidebar with contact details passed automatically Context Links are buttons in the conversation sidebar that open external URLs with contact and conversation details filled in. Agents click them to jump to CRMs, billing systems, or internal dashboards without copy-pasting identifiers. ## URL template variables These placeholders are replaced with the current contact's details when an agent clicks the link. | Variable | Description | | ------------------------ | ------------------------------------------------------- | | `{{email}}` | Contact's email | | `{{phone}}` | Contact's phone number | | `{{phone_country_code}}` | Contact's phone country code | | `{{external_user_id}}` | Contact's external user ID | | `{{contact_id}}` | Contact's internal Libredesk ID | | `{{first_name}}` | Contact's first name | | `{{last_name}}` | Contact's last name | | `{{conversation_uuid}}` | UUID of the current conversation | | `{{token}}` | Encrypted token containing all fields (requires secret) | All values except `{{contact_id}}` and `{{conversation_uuid}}` are URL-encoded automatically. ### Examples **Simple lookup by email:** ``` https://crm.example.com/contacts?email={{email}} ``` **Multiple parameters:** ``` https://billing.example.com/customer?ext_id={{external_user_id}}&email={{email}} ``` **Internal dashboard with conversation context:** ``` https://dashboard.internal/lookup?contact={{contact_id}}&conv={{conversation_uuid}} ``` **Encrypted token:** ``` https://api.example.com/auth/libredesk?token={{token}} ``` The receiving system decrypts the token to get all contact and agent fields in one payload, without exposing them in the URL. ## Encrypted token When you use `{{token}}`, Libredesk generates an AES-256-GCM encrypted, time-limited token containing all the above fields plus `agent_id`, `agent_email`, `iat`, and `exp`. The receiving system decrypts it with the shared secret. ### Decrypting the token ```python theme={null} import base64, json, time from cryptography.hazmat.primitives.ciphers.aead import AESGCM def decrypt_context_token(token, secret): raw = base64.b64decode(token) nonce = raw[:12] ciphertext = raw[12:] aesgcm = AESGCM(secret.encode('utf-8')) plaintext = aesgcm.decrypt(nonce, ciphertext, None) return json.loads(plaintext) # Usage secret = "your-32-character-shared-secret!" # exactly 32 characters payload = decrypt_context_token(token_from_url, secret) if payload["exp"] < time.time(): raise ValueError("Token has expired") print(payload["email"], payload["conversation_uuid"]) ``` # Email Templates Source: https://docs.libredesk.io/configuration/email-templates Customize outgoing emails with dynamic template expressions Outgoing emails can include expressions like `{{ .Recipient.FullName }}`, which pull fields from the conversation, contact, recipient, and author. Libredesk replaces them with real values when it sends the email. ## Outgoing email templates Edit the template under **Admin → Templates → Outgoing Email Templates**. It applies to every outgoing email, including replies to conversations, notifications, and other system-generated mail. ### Available variables #### Conversation variables | Variable | Value | | ------------------------------------- | ----------------------------------------------- | | `{{ .Conversation.ReferenceNumber }}` | The unique reference number of the conversation | | `{{ .Conversation.Subject }}` | The subject of the conversation | | `{{ .Conversation.Priority }}` | The priority level of the conversation | | `{{ .Conversation.UUID }}` | The unique identifier of the conversation | #### Contact variables | Variable | Value | | -------------------------- | ------------------------------------- | | `{{ .Contact.FirstName }}` | First name of the contact/customer | | `{{ .Contact.LastName }}` | Last name of the contact/customer | | `{{ .Contact.FullName }}` | Full name of the contact/customer | | `{{ .Contact.Email }}` | Email address of the contact/customer | #### Recipient variables | Variable | Value | | ---------------------------- | ------------------------------ | | `{{ .Recipient.FirstName }}` | First name of the recipient | | `{{ .Recipient.LastName }}` | Last name of the recipient | | `{{ .Recipient.FullName }}` | Full name of the recipient | | `{{ .Recipient.Email }}` | Email address of the recipient | The recipient is whoever receives the email. In replies to conversations, `Recipient` and `Contact` are the same person. They differ only in [email notification templates](#email-notification-templates), where the recipient is an agent. #### Author variables | Variable | Value | | ------------------------- | ----------------------------------- | | `{{ .Author.FirstName }}` | First name of the message author | | `{{ .Author.LastName }}` | Last name of the message author | | `{{ .Author.FullName }}` | Full name of the message author | | `{{ .Author.Email }}` | Email address of the message author | The author is the sender of the message. When Libredesk itself sends the email (automations, conversation continuity), the sender is the built-in System user, so these fields stay empty - otherwise emails would render "System" as the author's name. #### Message variables | Variable | Value | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `{{ .IsAIComposed }}` | `true` when the reply was written by an [AI assistant](/configuration/ai#assistants) rather than a person | | `{{ .IsContinuityEmail }}` | `true` when the email is a [conversation continuity](/configuration/livechat#conversation-continuity) email, i.e. unread live chat messages emailed to a contact who left the chat | Use them to disclose AI replies, or to skip parts of the template that don't fit an automated email: ```html theme={null} {{ if .IsContinuityEmail }}

You have new messages:

{{ else }}

Dear {{ .Recipient.FirstName }},

{{ end }} {{ if .IsAIComposed }}

This reply was composed by an AI assistant.

{{ end }} ``` ### Template functions These work in any template, alongside the variables above. | Function | Value | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `{{ RootURL }}` | Root URL of the Libredesk instance, set under **Admin → General** | | `{{ SiteName }}` | Site name, set under **Admin → General** | | `{{ LogoURL }}` | URL of the uploaded logo | | `{{ FaviconURL }}` | URL of the uploaded favicon | | `{{ Date "2006-01-02" }}` | Current date and time, formatted with a [Go layout](https://pkg.go.dev/time#pkg-constants). `{{ Date "" }}` uses the ANSIC layout | ### Using variables in replies and private notes The same expressions work when an agent types them in the reply editor or a private note, and in macros. Libredesk replaces them when the message is saved, so the agent sees the final text right away. ## Example outgoing email template Templates support HTML formatting. Use tags like `

` and `
` to control spacing and line breaks. Plain text without HTML tags renders without line breaks. ```html theme={null}

Dear {{ .Recipient.FirstName }},

{{ template "content" . }}

Best regards,
{{ .Author.FullName }}

---

Reference: {{ .Conversation.ReferenceNumber }}

{{ if .IsAIComposed }}

This reply was composed by an AI assistant.

{{ end }} ``` ### How the template wraps a message The template wraps every message an agent sends. Whatever the agent types in the editor goes where `{{ template "content" . }}` sits. So if the template already opens with "Dear `{{ .Recipient.FirstName }}`" and closes with "Best regards, `{{ .Author.FullName }}`", agents should write neither a greeting nor a sign-off. They write only the body. #### Correct usage **Message written in text editor:** ``` Thank you for contacting us. I've reviewed your account and can confirm that your refund has been processed. ``` **Email received by customer:** ``` Dear John, Thank you for contacting us. I've reviewed your account and can confirm that your refund has been processed. Best regards, Sarah Smith --- Reference: TKT-2024-001 ``` #### Duplicated greeting and sign-off **Message written in text editor:** ``` Hello John, Thank you for contacting us. I've reviewed your account and can confirm that your refund has been processed. Best regards, Sarah ``` **Email received by customer (with unwanted duplication):** ``` Dear John, Hello John, Thank you for contacting us. I've reviewed your account and can confirm that your refund has been processed. Best regards, Sarah Best regards, Sarah Smith --- Reference: TKT-2024-001 ``` ### Other template shapes A template with no greeting means agents write their own. A template that is only the content placeholder means agents write the whole email, greeting and sign-off included. Read the active template before deciding what to write. ## Email notification templates Edit these under **Admin → Templates → Email notification templates**. The default outgoing email template wraps each one, and the subject line takes the same expressions as the body. The first three go to agents, so `{{ .Recipient.* }}` is the agent being notified and author fields are empty. The CSAT request goes to the contact, as a normal reply in the conversation. Each template has its own variables: | Template | Variables | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Conversation assigned | Conversation, Contact, Recipient | | Mentioned in conversation | Conversation, Recipient, `{{ .MentionedBy.FirstName }}`, `{{ .MentionedBy.LastName }}`, `{{ .MentionedBy.FullName }}`, `{{ .MentionedBy.Email }}`, `{{ .Message.Content }}` (the private note), `{{ .Message.UUID }}` | | SLA breach warning, SLA breached | Conversation (`{{ .Conversation.Priority }}` is empty here), Recipient, `{{ .SLA.Metric }}` (First response, Resolution, or Next response), `{{ .SLA.DueIn }}` (time left, for the warning email), `{{ .SLA.OverdueBy }}` (time past the deadline, for the breach email) | | CSAT request | Everything an outgoing email gets (Conversation, Contact, Recipient, Author, `{{ .IsAIComposed }}`), plus `{{ .CSATLink }}` (rating page URL, append `?rating=1` to `?rating=5` for one-click rating) and `{{ .CSATUUID }}` | In the CSAT request, `{{ .Author.* }}` holds the person who resolved the conversation, and is empty when an automation resolved it. # Help Center Source: https://docs.libredesk.io/configuration/help-center Publish a searchable knowledge base with collections, articles in multiple languages, and customize it however you want A help center is a public site, served by Libredesk, where customers browse and search your articles. You can run more than one. Each has its own content, languages, appearance and domain. Published articles can also be used by the AI assistant to answer conversations. ## Create a help center Go to **Admin → Help center → New help center**. Fill in the name and the slug that goes in its URL. **Docs** gives you a documentation layout: a top navigation bar, a sidebar listing collections, and quick search on Ctrl+K. **Classic** gives you a landing page with a hero banner, a large search bar and collection cards. ## URLs By default the help center is served from your Libredesk instance: ``` https://support.example.com/hc// https://support.example.com/hc///collections/ https://support.example.com/hc///articles/ ``` ## Custom domain A help center can be served from its own domain or subdomain, for example `https://help.example.com`, where it sits at the root and the paths above drop the `/hc/` prefix. Two help centers cannot share a domain. Set **Custom domain** to the domain's root URL in the help center's settings, then: Point the domain at the same server as your Libredesk instance: an `A` record to its IP, or a `CNAME` to the hostname you already serve Libredesk on. Libredesk decides which help center to serve from the `Host` header of the request, so the proxy has to forward the original host. Save this as `/etc/nginx/sites-available/libredesk-help`: ```nginx theme={null} server { listen 80; server_name help.example.com; location / { proxy_pass http://localhost:9000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Client-IP $remote_addr; } } ``` Then enable it and reload nginx: ```bash theme={null} sudo ln -s /etc/nginx/sites-available/libredesk-help /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx ``` `proxy_set_header Host $host;` is the line that matters. Without it nginx sends the upstream's host, no help center matches, and visitors land on the agent login page instead of your help center. ## Collections and articles Collections group articles and can be nested up to three levels deep. To change the order readers see, drag a row by its handle in the content tree. Only published articles appear on the public site, and **Preview** shows a draft the way its public page will look. ## Languages Add every language you plan to write in under **Supported languages**, then pick the **Default language** that visitors land on. A language picker appears in the navigation bar once there is more than one. A collection or article belongs to a single language, and an article has to sit in a collection of the same language. A language that still has collections or articles in it cannot be removed. ## AI answers On any article the AI assistant may answer from, turn on **Allow AI assistants to use this article**. Nothing happens until the article is published. Enable it in one language only, not in every translation. AI has to be configured for this to work, see [AI](/configuration/ai). ## Appearance **Customize** covers the logo, favicon, accent colour, header and hero, announcement bar, navigation and footer links, and what the landing and article pages show. For anything the settings do not cover, **Custom code** takes custom CSS and JS, injected into every page. ## SEO Set the page title and meta description for the home page, and the meta fields on each article. Libredesk serves `/robots.txt` and a sitemap index at `/sitemap.xml`, plus a per-language sitemap at `/hc///sitemap.xml`. Both are scoped to the host that asked for them, so a custom domain only advertises its own help center. ## Search insights **Search insights** lists the top searches visitors ran and the ones that came back empty, which is where you find the articles you are missing. ## Permissions Managing help centers, collections and articles needs the **Manage help centers** permission, which you add to a role under **Admin → Roles**. # Live Chat Source: https://docs.libredesk.io/configuration/livechat Embed the Libredesk chat widget on your website and verify user identity with JWTs The Libredesk live chat widget embeds on any website as a single ` ``` `baseURL` is the URL of your Libredesk instance. `inboxID` is the UUID shown on the inbox's **Installation** tab. Anyone visiting the page can now open the widget and start a conversation. Every session is treated as a new anonymous visitor unless you add identity verification. ## Identity verification If users are already signed into your product, pass a signed JWT so the widget loads with their existing Libredesk contact. Without verification, the widget cannot tell a returning user apart from a new visitor on a new device. ### How it works 1. Your server signs a JWT with the inbox **Secret key** (set under the **Security** tab). 2. Your page passes that JWT to the widget via `userJWT`. 3. Libredesk verifies the signature, then upserts the contact by `external_user_id`. The signing algorithm is **HS256** (HMAC-SHA256). The secret never leaves your server. ### JWT payload ```json theme={null} { "external_user_id": "your_app_user_123", "email": "user@example.com", "first_name": "John", "last_name": "Doe", "phone_number": "9876543210", "phone_number_country_code": "IN", "exp": 1735689600, "contact_custom_attributes": { "plan": "premium", "company": "Acme Inc" } } ``` | Field | Required | Description | | --------------------------- | -------- | ----------------------------------------------------------------------- | | `external_user_id` | Yes | Stable unique ID from your system. Used to match returning users. | | `email` | Yes | User's email address. | | `first_name` | Yes | User's first name. | | `last_name` | No | User's last name. | | `phone_number` | No | User's phone number. | | `phone_number_country_code` | No | ISO 3166-1 alpha-2 country code for the phone number (e.g. `IN`, `US`). | | `exp` | Yes | Unix timestamp (seconds) when the token expires. | | `contact_custom_attributes` | No | Object of custom attributes written to the contact record on login. | ### Sign the token on your server ```python Python theme={null} import jwt, time payload = { "external_user_id": "your_app_user_123", "email": "user@example.com", "first_name": "John", "exp": int(time.time()) + 3600, } token = jwt.encode(payload, SECRET, algorithm="HS256") ``` ```javascript Node.js theme={null} const jwt = require('jsonwebtoken'); const token = jwt.sign({ external_user_id: 'your_app_user_123', email: 'user@example.com', first_name: 'John', exp: Math.floor(Date.now() / 1000) + 3600, }, SECRET, { algorithm: 'HS256' }); ``` ```go Go theme={null} import ( "time" "github.com/golang-jwt/jwt/v5" ) claims := jwt.MapClaims{ "external_user_id": "your_app_user_123", "email": "user@example.com", "first_name": "John", "exp": time.Now().Add(time.Hour).Unix(), } token, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims). SignedString([]byte(SECRET)) ``` Never embed the inbox secret in client-side code. Sign the JWT on your server and inject it into the page at render time, or fetch it from an authenticated endpoint. ### Pass the token to the widget ```html theme={null} ``` ## JavaScript API Once the widget loads, `window.Libredesk` exposes these methods: | Method | Description | | ----------------------------------- | -------------------------------------------------------- | | `Libredesk.show()` | Open the widget. | | `Libredesk.hide()` | Close the widget. | | `Libredesk.toggle()` | Toggle between open and closed. | | `Libredesk.setUser(jwt)` | Log a user in after page load with a signed JWT. | | `Libredesk.logout()` | Clear the current session and reset to anonymous. | | `Libredesk.onShow(fn)` | Register a callback fired when the widget opens. | | `Libredesk.onHide(fn)` | Register a callback fired when the widget closes. | | `Libredesk.onUnreadCountChange(fn)` | Register a callback fired with the unread message count. | Example: ```javascript theme={null} window.Libredesk.onUnreadCountChange(function (count) { document.title = count > 0 ? `(${count}) Inbox` : 'Inbox'; }); ``` ## Conversation continuity Contacts don't have to stay in the live chat. If they leave before the conversation is finished, Libredesk can automatically email them any unread messages. | Setting | Description | | ------------------------ | ------------------------------------------------------------------------------------- | | `offline_threshold` | How long agents must be offline before Libredesk sends a fallback email (e.g. `10m`). | | `max_messages_per_email` | Maximum number of messages per email. Additional messages are sent in a new email. | | `min_email_interval` | Minimum time between fallback emails for the same conversation. | Replies to the email thread are added back to the same live chat conversation. ## Security ### Trusted domains Under the **Security** tab, list the domains allowed to embed the widget. Requests from other origins are rejected. Supports wildcards: ``` example.com *.example.com staging.example.com ``` Leave this empty to allow all origins. Always set it in production. ### Blocked IPs Block specific IPs or CIDR ranges from opening the widget: ``` 192.168.1.0/24 10.0.0.1 2001:db8::/32 ``` ### Session duration Controls how long a widget session stays authenticated before the JWT must be re-verified. Default is `10h`. Format accepts `s`, `m`, `h` (e.g. `30m`, `24h`). # SSO Setup Source: https://docs.libredesk.io/configuration/sso Configure Single Sign-On with OpenID Connect providers Agents can sign in through an external OpenID Connect provider such as Google or Keycloak. You still create agent accounts in Libredesk yourself. Signing in with SSO does not create an account. ## Configuration Providers differ in the details, so check your provider's documentation for anything not covered here. In your provider's admin console, create a new OpenID Connect application/client. Retrieve: * Client ID * Client Secret In Libredesk, navigate to Security → SSO and click New SSO and enter the following details: * Provider URL (e.g., the URL of your OpenID provider) * Client ID * Client Secret * A descriptive name for the connection After saving, copy the generated Callback URL from Libredesk and add it as a valid redirect URI in your provider's client settings. ## Provider examples ### Keycloak In Keycloak, navigate to Clients and click Create: * Client ID (e.g., `libredesk-app`) * Client Protocol: `openid-connect` * Root URL and Web Origins: your app domain (e.g., `https://ticket.example.com`) * Under Authentication flow, uncheck everything except the standard flow * Click save Go to the credentials tab: * Ensure client authenticator is set to `Client Id and Secret` * Note down the generated client secret In Libredesk, go to Admin → Security → SSO and click New SSO: * Provider URL (e.g., `https://keycloak.example.com/realms/yourrealm`) * Name (e.g., `Keycloak`) * Client ID * Client secret * Click save 1. After saving, click on the three dots and choose Edit to open the new SSO entry 2. Copy the generated Callback URL from Libredesk 3. Back in Keycloak, edit the client and add the Callback URL to Valid Redirect URIs: * e.g., `https://ticket.example.com/api/v1/oidc/1/finish` # SSRF protection Source: https://docs.libredesk.io/configuration/ssrf-protection Optional guard for admin-configured outbound URLs Libredesk lets admins configure URLs that the server fetches on their behalf. This guard blocks those outbound requests from reaching private, loopback, link-local, and cloud-metadata IP ranges (SSRF protection). It covers every admin-configured outbound URL: * **Webhooks** - event delivery endpoints * **SSO** - OIDC provider discovery * **AI** - the AI provider base URL and custom AI tool calls ## Disabled by default The guard is **off by default**. In a self-hosted, single-tenant deployment the admin already controls the infrastructure and sets these URLs, so reaching internal hosts is not a privilege boundary. It is expected, and common setups depend on it: * An internal webhook receiver * An OIDC provider on a private network * A local model endpoint, such as Ollama on `localhost` or a LAN address Enabling the guard by default would break those legitimate cases. ## Opting in Turn it on when the outbound URLs come from parties you do not fully trust, such as a multi-tenant or hosted deployment. Configure it in `config.toml`: ```toml theme={null} [ssrf] # Block outbound requests to private/loopback/link-local/cloud-metadata IPs. enabled = true # CIDR ranges allowed to bypass the guard when enabled, for reaching known internal hosts. allowed_cidrs = ["10.0.0.0/8", "192.168.1.10/32"] ``` Use `allowed_cidrs` to carve out known internal hosts you still need to reach while the guard is on. # Webhooks Source: https://docs.libredesk.io/configuration/webhooks Receive real-time HTTP notifications for Libredesk events When something happens in your Libredesk instance, Libredesk sends an HTTP POST to a URL you choose. The body is JSON containing the event name, a timestamp, and the conversation or message it happened to. Use webhooks to push Libredesk activity into your own systems. ## Configuration Go to **Admin → Integrations → Webhooks** in your Libredesk dashboard Click **Create Webhook** and fill in: * **Name**: a name you will recognize later * **URL**: where Libredesk sends the payload * **Events**: the events to subscribe to * **Secret**: optional, used to sign the payload * **Status**: enabled or disabled Use the test button to send a sample payload to verify your endpoint is working ## Security ### Signature verification When you set a secret, Libredesk signs the payload with HMAC-SHA256 and sends the result in the `X-Libredesk-Signature` header as `sha256=`. ```python Python theme={null} import hmac import hashlib def verify_signature(payload, signature, secret): expected_signature = hmac.new( secret.encode('utf-8'), payload, hashlib.sha256 ).hexdigest() return hmac.compare_digest(f"sha256={expected_signature}", signature) ``` ```javascript Node.js theme={null} const crypto = require('crypto'); function verifySignature(payload, signature, secret) { const expectedSignature = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return signature === `sha256=${expectedSignature}`; } ``` ```go Go theme={null} import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" ) func verifySignature(payload []byte, signature, secret string) bool { h := hmac.New(sha256.New, []byte(secret)) h.Write(payload) expectedSignature := fmt.Sprintf("sha256=%s", hex.EncodeToString(h.Sum(nil))) return hmac.Equal([]byte(signature), []byte(expectedSignature)) } ``` ### Headers Every request carries these headers: | Header | Description | | ----------------------- | ---------------------------------------- | | `Content-Type` | Always `application/json` | | `User-Agent` | `Libredesk-Webhook/` | | `X-Libredesk-Signature` | HMAC signature (if secret is configured) | ### SSRF protection Webhook URLs are one of several admin-configured outbound URLs covered by Libredesk's optional SSRF guard, which is disabled by default. See [SSRF protection](/configuration/ssrf-protection) for details and how to enable it. ## Events ### Conversation events Triggered when a new conversation is created. ```json theme={null} { "event": "conversation.created", "timestamp": "2025-06-15T10:30:00Z", "payload": { "id": 123, "created_at": "2025-06-15T10:30:00Z", "updated_at": "2025-06-15T10:30:00Z", "uuid": "550e8400-e29b-41d4-a716-446655440000", "contact_id": 456, "inbox_id": 1, "reference_number": "100", "priority": "Medium", "priority_id": 2, "status": "Open", "status_id": 1, "subject": "Help with account setup", "inbox_name": "Support", "inbox_channel": "email", "contact": { "id": 456, "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "type": "contact" }, "custom_attributes": {}, "tags": [] } } ``` Triggered when a conversation's status is updated. ```json theme={null} { "event": "conversation.status_changed", "timestamp": "2025-06-15T10:35:00Z", "payload": { "conversation_uuid": "550e8400-e29b-41d4-a716-446655440000", "previous_status": "Open", "new_status": "Resolved", "snooze_until": "", "actor_id": 789, "conversation": { "id": 123, "created_at": "2025-06-15T10:30:00Z", "updated_at": "2025-06-15T10:35:00Z", "uuid": "550e8400-e29b-41d4-a716-446655440000", "contact_id": 456, "inbox_id": 1, "closed_at": null, "resolved_at": "2025-06-15T10:35:00Z", "reference_number": "100", "priority": "Medium", "priority_id": 2, "status": "Resolved", "status_id": 3, "first_reply_at": "2025-06-15T10:31:00Z", "last_reply_at": "2025-06-15T10:34:00Z", "assigned_user_id": 789, "assigned_team_id": null, "waiting_since": null, "subject": "Help with account setup", "inbox_mail": "support@example.com", "inbox_name": "Support", "inbox_channel": "email", "tags": [], "meta": [], "custom_attributes": {}, "last_message_at": "2025-06-15T10:34:00Z", "last_message": "Thank you for your help!", "last_message_sender": "contact", "last_interaction": "Thank you for your help!", "last_interaction_at": "2025-06-15T10:34:00Z", "last_interaction_sender": "contact", "contact": { "id": 456, "created_at": "2025-06-01T09:00:00Z", "updated_at": "2025-06-15T10:30:00Z", "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "type": "contact", "availability_status": "offline", "avatar_url": null, "phone_number": null, "phone_number_country_code": null, "custom_attributes": {}, "enabled": true, "last_active_at": null, "last_login_at": null }, "sla_policy_id": null, "sla_policy_name": null, "applied_sla_id": null, "first_response_deadline_at": null, "resolution_deadline_at": null, "next_response_deadline_at": null, "next_response_met_at": null, "previous_conversations": null } } } ``` Triggered when a conversation is assigned to a user. ```json theme={null} { "event": "conversation.assigned", "timestamp": "2025-06-15T10:32:00Z", "payload": { "conversation_uuid": "550e8400-e29b-41d4-a716-446655440000", "assigned_to": 789, "actor_id": 789, "conversation": { "id": 123, "created_at": "2025-06-15T10:30:00Z", "updated_at": "2025-06-15T10:32:00Z", "uuid": "550e8400-e29b-41d4-a716-446655440000", "contact_id": 456, "inbox_id": 1, "closed_at": null, "resolved_at": null, "reference_number": "100", "priority": "Medium", "priority_id": 2, "status": "Open", "status_id": 1, "first_reply_at": null, "last_reply_at": null, "assigned_user_id": 789, "assigned_team_id": null, "waiting_since": "2025-06-15T10:30:00Z", "subject": "Help with account setup", "inbox_mail": "support@example.com", "inbox_name": "Support", "inbox_channel": "email", "tags": [], "meta": [], "custom_attributes": {}, "last_message_at": "2025-06-15T10:30:00Z", "last_message": "I need help setting up my account", "last_message_sender": "contact", "last_interaction": "I need help setting up my account", "last_interaction_at": "2025-06-15T10:30:00Z", "last_interaction_sender": "contact", "contact": { "id": 456, "created_at": "2025-06-01T09:00:00Z", "updated_at": "2025-06-15T10:30:00Z", "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "type": "contact", "availability_status": "offline", "avatar_url": null, "phone_number": null, "phone_number_country_code": null, "custom_attributes": {}, "enabled": true, "last_active_at": null, "last_login_at": null }, "sla_policy_id": null, "sla_policy_name": null, "applied_sla_id": null, "first_response_deadline_at": null, "resolution_deadline_at": null, "next_response_deadline_at": null, "next_response_met_at": null, "previous_conversations": null } } } ``` Triggered when a conversation is unassigned from a user. ```json theme={null} { "event": "conversation.unassigned", "timestamp": "2025-06-15T10:40:00Z", "payload": { "conversation_uuid": "550e8400-e29b-41d4-a716-446655440000", "actor_id": 789, "conversation": { "id": 123, "created_at": "2025-06-15T10:30:00Z", "updated_at": "2025-06-15T10:40:00Z", "uuid": "550e8400-e29b-41d4-a716-446655440000", "contact_id": 456, "inbox_id": 1, "closed_at": null, "resolved_at": null, "reference_number": "100", "priority": "Medium", "priority_id": 2, "status": "Open", "status_id": 1, "first_reply_at": "2025-06-15T10:31:00Z", "last_reply_at": "2025-06-15T10:35:00Z", "assigned_user_id": null, "assigned_team_id": null, "waiting_since": "2025-06-15T10:35:00Z", "subject": "Help with account setup", "inbox_mail": "support@example.com", "inbox_name": "Support", "inbox_channel": "email", "tags": [], "meta": [], "custom_attributes": {}, "last_message_at": "2025-06-15T10:35:00Z", "last_message": "I still need help", "last_message_sender": "contact", "last_interaction": "I still need help", "last_interaction_at": "2025-06-15T10:35:00Z", "last_interaction_sender": "contact", "contact": { "id": 456, "created_at": "2025-06-01T09:00:00Z", "updated_at": "2025-06-15T10:30:00Z", "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "type": "contact", "availability_status": "offline", "avatar_url": null, "phone_number": null, "phone_number_country_code": null, "custom_attributes": {}, "enabled": true, "last_active_at": null, "last_login_at": null }, "sla_policy_id": null, "sla_policy_name": null, "applied_sla_id": null, "first_response_deadline_at": null, "resolution_deadline_at": null, "next_response_deadline_at": null, "next_response_met_at": null, "previous_conversations": null } } } ``` Triggered when tags are added or removed from a conversation. ```json theme={null} { "event": "conversation.tags_changed", "timestamp": "2025-06-15T10:45:00Z", "payload": { "conversation_uuid": "550e8400-e29b-41d4-a716-446655440000", "previous_tags": ["bug"], "new_tags": ["bug", "priority"], "actor_id": 789, "conversation": { "id": 123, "created_at": "2025-06-15T10:30:00Z", "updated_at": "2025-06-15T10:45:00Z", "uuid": "550e8400-e29b-41d4-a716-446655440000", "contact_id": 456, "inbox_id": 1, "closed_at": null, "resolved_at": null, "reference_number": "100", "priority": "Medium", "priority_id": 2, "status": "Open", "status_id": 1, "first_reply_at": "2025-06-15T10:31:00Z", "last_reply_at": "2025-06-15T10:35:00Z", "assigned_user_id": 789, "assigned_team_id": null, "waiting_since": null, "subject": "Help with account setup", "inbox_mail": "support@example.com", "inbox_name": "Support", "inbox_channel": "email", "tags": ["bug", "priority"], "meta": [], "custom_attributes": {}, "last_message_at": "2025-06-15T10:35:00Z", "last_message": "Thank you for your help!", "last_message_sender": "contact", "last_interaction": "Thank you for your help!", "last_interaction_at": "2025-06-15T10:35:00Z", "last_interaction_sender": "contact", "contact": { "id": 456, "created_at": "2025-06-01T09:00:00Z", "updated_at": "2025-06-15T10:30:00Z", "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "type": "contact", "availability_status": "offline", "avatar_url": null, "phone_number": null, "phone_number_country_code": null, "custom_attributes": {}, "enabled": true, "last_active_at": null, "last_login_at": null }, "sla_policy_id": null, "sla_policy_name": null, "applied_sla_id": null, "first_response_deadline_at": null, "resolution_deadline_at": null, "next_response_deadline_at": null, "next_response_met_at": null, "previous_conversations": null } } } ``` ### Message events Triggered when a new message is created in a conversation. ```json theme={null} { "event": "message.created", "timestamp": "2025-06-15T10:33:00Z", "payload": { "id": 987, "created_at": "2025-06-15T10:33:00Z", "updated_at": "2025-06-15T10:33:00Z", "uuid": "123e4567-e89b-12d3-a456-426614174000", "type": "outgoing", "status": "sent", "conversation_id": 123, "content": "

Hello! How can I help you today?

", "text_content": "Hello! How can I help you today?", "content_type": "html", "private": false, "sender_id": 789, "sender_type": "agent", "attachments": [] } } ```
Triggered when an existing message is updated. ```json theme={null} { "event": "message.updated", "timestamp": "2025-06-15T10:34:00Z", "payload": { "id": 987, "created_at": "2025-06-15T10:33:00Z", "updated_at": "2025-06-15T10:34:00Z", "uuid": "123e4567-e89b-12d3-a456-426614174000", "type": "outgoing", "status": "sent", "conversation_id": 123, "content": "

Hello! How can I help you today? (Updated)

", "text_content": "Hello! How can I help you today? (Updated)", "content_type": "html", "private": false, "sender_id": 789, "sender_type": "agent", "attachments": [] } } ```
## Delivery and retries * Deliveries run in a background worker pool. * The request timeout is set in `config.toml`. * Failed deliveries are not retried. * Events can be dropped when the delivery queue is full. The queue size is set in `config.toml`. Because there are no retries, verify the signature and reply within 5 seconds, then do the actual work after you have responded. An endpoint that blocks on slow processing will time out and lose the event. ## Testing Both of these give you a throwaway URL that shows the payloads Libredesk sends: Generate a temporary URL to inspect webhook payloads Collect and inspect HTTP requests ## Example integration An Express server that verifies the signature, replies immediately, and processes the event afterwards: ```javascript theme={null} const express = require('express'); const crypto = require('crypto'); const app = express(); // Middleware to capture raw body for signature verification app.use(express.raw({ type: 'application/json' })); function verifySignature(payload, signature, secret) { const expectedSignature = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return signature === `sha256=${expectedSignature}`; } app.post('/webhook', (req, res) => { const signature = req.headers['x-libredesk-signature']; const secret = process.env.WEBHOOK_SECRET; // Verify signature if (secret && !verifySignature(req.body, signature, secret)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(req.body); // Process event asynchronously setImmediate(() => { processWebhookEvent(event); }); // Respond immediately res.status(200).send('OK'); }); function processWebhookEvent(event) { console.log(`Received event: ${event.event}`); switch(event.event) { case 'conversation.created': // Handle new conversation break; case 'message.created': // Handle new message break; // Add more cases as needed } } app.listen(3000, () => { console.log('Webhook server listening on port 3000'); }); ``` # Developer Setup Source: https://docs.libredesk.io/contributing/developer-setup Set up your development environment for Libredesk Libredesk is a monorepo with a Go backend and two Vue.js frontend apps: the main agent dashboard and the live chat widget. Both use Shadcn for UI components. ## Prerequisites * Go * Node.js and pnpm (if working on frontend) * Redis * PostgreSQL (≥13) ## First-time setup ```bash theme={null} git clone https://github.com/abhinavxd/libredesk.git cd libredesk ``` Copy `config.sample.toml` as `config.toml` and add your configuration: ```bash theme={null} cp config.sample.toml config.toml ``` Build the libredesk binary and run the DB setup: ```bash theme={null} make ./libredesk --install ``` Sets up the database and prompts you for the System user password. ## Running the dev environment Run the backend and both frontend apps in development mode: ```bash theme={null} make run-backend ``` This starts the libredesk backend dev server on `:9000` ```bash theme={null} make run-frontend-main ``` This starts the agent dashboard in dev mode using pnpm on `:8000` Requests are proxied to the backend running on `:9000` (check `vite.config.js` for proxy config) ```bash theme={null} make run-frontend-widget ``` This starts the live chat widget in dev mode on `:8001`, also proxied to the backend on `:9000` Only needed if you are working on the widget. ```bash theme={null} make run-backend ``` Access the backend API at `http://localhost:9000` ```bash theme={null} make run-frontend-main ``` Access the main app at `http://localhost:8000` ```bash theme={null} make run-frontend-widget ``` Access the widget at `http://localhost:8001` ## Production build To build a production-ready binary: ```bash theme={null} make ``` This builds the Go binary and both frontend apps, embeds the static assets, and produces a single self-contained binary: `libredesk`. To build just one app: `make frontend-build-main` or `make frontend-build-widget`. # Translations Source: https://docs.libredesk.io/contributing/translations Help translate Libredesk into different languages Libredesk translations are managed on Crowdin. Pick a language, translate the untranslated strings, and the translations sync back into the app. Libredesk on Crowdin # API Getting Started Source: https://docs.libredesk.io/getting-started/api-getting-started Access the Libredesk API to interact with your instance programmatically You can access the Libredesk API to interact with your instance programmatically by generating API keys for any agent. Some endpoints are not documented yet. Pull requests to the docs are welcome. ## Generating API keys Go to **Admin → Teammate → Agent → Edit** Libredesk shows an API key and an API secret for that agent Keep both the API key and API secret secure You can revoke or regenerate API keys at any time from the same page ## Using the API Libredesk supports two authentication schemes: ### Basic authentication ```bash theme={null} curl -X GET "https://your-libredesk-instance.com/api/endpoint" \ -H "Authorization: Basic " ``` ### Token authentication ```bash theme={null} curl -X GET "https://your-libredesk-instance.com/api/endpoint" \ -H "Authorization: token your_api_key:your_api_secret" ``` ## API reference Every documented endpoint is listed in the [API reference](/api-reference/introduction). Browse endpoints with request and response examples # Installation Source: https://docs.libredesk.io/getting-started/installation Deploy Libredesk using Docker, binary, or from source Libredesk ships as a single binary and needs PostgreSQL and Redis to run. Install it with Docker, from a release binary, or from source. ## Installation methods ## Docker installation The latest image is available on DockerHub at [libredesk/libredesk:latest](https://hub.docker.com/r/libredesk/libredesk) ### Quick start with Docker Compose ```bash theme={null} # Download the compose file and the sample config file curl -LO https://github.com/abhinavxd/libredesk/raw/main/docker-compose.yml curl -LO https://github.com/abhinavxd/libredesk/raw/main/config.sample.toml ``` ```bash theme={null} # Copy the config.sample.toml to config.toml and edit as needed cp config.sample.toml config.toml ``` Edit `config.toml` with your database credentials and preferences. ```bash theme={null} # Run the services in the background docker compose up -d ``` ```bash theme={null} # Set System user password docker exec -it libredesk_app ./libredesk --set-system-user-password ``` Visit `http://localhost:9000` and login with: * **Email**: `System` * **Password**: The password you just set ## Binary installation **Prerequisites:** * PostgreSQL ≥13 * Redis Download the [latest release](https://github.com/abhinavxd/libredesk/releases) for your platform and extract the libredesk binary. ```bash theme={null} # Download the sample config file curl -LO https://github.com/abhinavxd/libredesk/raw/main/config.sample.toml # Copy the config.sample.toml to config.toml and edit as needed cp config.sample.toml config.toml ``` Edit `config.toml` with your database credentials and preferences. With the default `fs` upload provider, files are stored in the directory set by `upload.fs.upload_path` (`uploads`, relative to the working directory). Libredesk does not create this directory, and uploads fail if it is missing or not writable. ```bash theme={null} mkdir uploads chmod 755 uploads ``` If Libredesk runs as a different user than the one that created the directory, give that user ownership with `chown`. ```bash theme={null} ./libredesk --install ``` Creates the tables in PostgreSQL. To set the System user password during installation, use the environment variable: ```bash theme={null} LIBREDESK_SYSTEM_USER_PASSWORD=your_password ./libredesk --install ``` ```bash theme={null} # Set System user password ./libredesk --set-system-user-password ``` ```bash theme={null} ./libredesk ``` Visit `http://localhost:9000` and login with: * **Email**: `System` * **Password**: The password you just set ## Compile from source Build the latest unreleased version from the `main` branch. **Prerequisites:** * PostgreSQL ≥13 * Redis * Go (latest version) * Node.js (≥18) * pnpm ```bash theme={null} git clone git@github.com:abhinavxd/libredesk.git cd libredesk ``` ```bash theme={null} make ``` Generates the `libredesk` binary in the current directory. ```bash theme={null} # Download the sample config file curl -LO https://github.com/abhinavxd/libredesk/raw/main/config.sample.toml # Copy the config.sample.toml to config.toml and edit as needed cp config.sample.toml config.toml ``` Edit `config.toml` with your database credentials and preferences. With the default `fs` upload provider, files are stored in the directory set by `upload.fs.upload_path` (`uploads`, relative to the working directory). Libredesk does not create this directory, and uploads fail if it is missing or not writable. ```bash theme={null} mkdir uploads chmod 755 uploads ``` If Libredesk runs as a different user than the one that created the directory, give that user ownership with `chown`. ```bash theme={null} ./libredesk --install ``` Creates the tables in PostgreSQL. To set the System user password during installation, use the environment variable: ```bash theme={null} LIBREDESK_SYSTEM_USER_PASSWORD=your_password ./libredesk --install ``` ```bash theme={null} # Set System user password ./libredesk --set-system-user-password ``` ```bash theme={null} ./libredesk ``` Visit `http://localhost:9000` and login with: * **Email**: `System` * **Password**: The password you just set ## Configuration via environment variables Instead of using a `config.toml` file, you can configure Libredesk entirely through environment variables. * All environment variables use the `LIBREDESK_` prefix * Use double underscore (`__`) for nested configuration keys * To use environment variables exclusively (without a config file), pass `--config=""` **Example:** | TOML Config | Environment Variable | | ----------------------------------- | ------------------------------------------- | | `upload.fs.upload_path = "uploads"` | `LIBREDESK_UPLOAD__FS__UPLOAD_PATH=uploads` | | `db.host = "localhost"` | `LIBREDESK_DB__HOST=localhost` | ## Nginx reverse proxy (recommended) We recommend running Libredesk behind Nginx as a reverse proxy. Install nginx if you don't have it: ```bash theme={null} # Debian / Ubuntu sudo apt update && sudo apt install nginx # RHEL / Fedora / CentOS sudo dnf install nginx && sudo systemctl enable --now nginx ``` The proxy must set `X-Client-IP` to `$remote_addr`. Libredesk reads this header to identify the client for rate limiting and audit logs, and without it every request looks like it came from the proxy. Save the following as `/etc/nginx/sites-available/libredesk`: ```nginx theme={null} server { listen 80; server_name your-domain.com; client_max_body_size 30M; location / { proxy_pass http://localhost:9000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Client-IP $remote_addr; # If behind Cloudflare (or another CDN), forward the CDN's real-client header # instead, AND firewall nginx to only accept connections from the CDN's IP range, # otherwise clients can bypass the CDN and spoof this header directly. # proxy_set_header X-Client-IP $http_cf_connecting_ip; # Cloudflare proxy_cache_bypass $http_upgrade; } } ``` Then enable it and reload nginx: ```bash theme={null} sudo ln -s /etc/nginx/sites-available/libredesk /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx ``` # Upgrade Guide Source: https://docs.libredesk.io/getting-started/upgrade How to upgrade Libredesk to the latest version Always take a backup of the Postgres database before upgrading Libredesk. Dump the database to a file with `pg_dump`. This works for both Docker and non-Docker setups, since Docker exposes Postgres on `localhost:5432`. Replace the user and database name (`libredesk`) if you changed them in your config. ```bash theme={null} pg_dump -h localhost -U libredesk libredesk > libredesk_backup.sql ``` ## Upgrade methods Stop the running libredesk binary process. Download the [latest release](https://github.com/abhinavxd/libredesk/releases) and extract the libredesk binary to overwrite the previous version. ```bash theme={null} ./libredesk --upgrade ``` Upgrades are idempotent, so running this more than once does nothing. ```bash theme={null} ./libredesk ``` ```bash theme={null} docker compose down app ``` ```bash theme={null} docker compose pull ``` ```bash theme={null} docker compose up app -d ``` # Deploy on Railway Source: https://docs.libredesk.io/hosting-providers/railway One-click deploy Libredesk on Railway with managed PostgreSQL and Redis [Railway](https://railway.com) provisions a Libredesk service alongside managed PostgreSQL and Redis from a single template. The database schema installs automatically on first deploy. ## Deploy Deploy on Railway Sign in to Railway, then set `LIBREDESK_SYSTEM_USER_PASSWORD` in the deploy form. It must be at least 10 characters with uppercase, lowercase, numeric, and special characters (e.g. `MyPassword@123`). Railway provisions PostgreSQL, Redis, and the Libredesk app, and assigns a `*.up.railway.app` domain to the Libredesk service. The schema installs automatically on first boot. Visit the assigned domain and sign in with: * **Email**: `System` * **Password**: the password from step 1 ## Configuration To override any Libredesk setting, add an environment variable under the **Libredesk** service's **Variables** tab. All variables use the `LIBREDESK_` prefix with double underscores (`__`) for nested keys. | Setting | Environment variable | | -------------------------------------------- | ----------------------------------------- | | Max attachment / request body size (bytes) | `LIBREDESK_APP__SERVER__MAX_BODY_SIZE` | | Session lifetime | `LIBREDESK_APP__SERVER__SESSION_LIFETIME` | | Log level (`debug`, `info`, `warn`, `error`) | `LIBREDESK_APP__LOG_LEVEL` | See the [full configuration reference](/getting-started/installation#configuration-via-environment-variables) for the complete list. Don't change the auto-injected database and Redis variables. Railway manages those references. ## Updating Libredesk Redeploy the **Libredesk** service from the Railway dashboard. It pulls the latest image and runs any pending migrations on startup. # Introduction Source: https://docs.libredesk.io/introduction Modern, open source, self-hosted omnichannel customer support desk. Live chat, email, and more — packed into a single binary. Libredesk is a free and open-source omnichannel customer support desk with built-in live chat, email, and more. The backend is written in Go and the frontend in Vue JS with ShadcN for UI components. Libredesk Libredesk Visit [libredesk.io](https://libredesk.io) for more info. Check out the [**live demo**](https://demo.libredesk.io/). ## Features * **Omnichannel inbox**\ Live chat, email, and more — all in one inbox. Manage every conversation from a single, unified interface. * **Live chat widget**\ Embed a real-time chat widget on your website. Engage visitors instantly and handle live conversations right from your support desk. * **Help center**\ Publish a searchable knowledge base with collections, articles in multiple languages, and customize it however you want. * **Granular permissions**\ Create custom roles with granular permissions for teams and individual agents. * **Automations**\ Eliminate repetitive tasks with powerful automation rules. Auto-tag, assign, and route conversations based on custom conditions. * **CSAT surveys**\ Measure customer satisfaction with automated surveys. * **Macros**\ Save frequently sent messages as templates. With one click, send saved responses, set tags, and more. * **Organization**\ Keep conversations organized with tags, custom statuses for conversations, and snoozing. Find any conversation instantly from the search bar. * **Auto assignment**\ Distribute workload with auto assignment rules. Auto-assign conversations based on agent capacity or custom criteria. * **SLA management**\ Set and track response time targets. Get notified when conversations are at risk of breaching SLA commitments. * **Custom attributes**\ Create custom attributes for contacts or conversations such as the subscription plan or the date of their first purchase. * **AI-assist**\ Instantly rewrite responses with AI to make them more friendly, professional, or polished. * **Activity logs**\ Track all actions performed by agents and admins—updates and key events across the system—for auditing and accountability. ## Quick links Get Libredesk up and running Try Libredesk in action View source code Visit our website # Roles & Permissions Source: https://docs.libredesk.io/roles/overview Control what agents can see and do in Libredesk with roles A role is a named set of permissions. Every agent is assigned one or more roles, and the agent's effective access is the union of the permissions across those roles. Roles are managed under **Admin → Teams → Roles**. ## Default roles Libredesk ships with two roles. | Role | Description | | ------- | ----------------------------------------------------------------------------------------------------------------------------- | | `Admin` | Complete access to everything, including all settings. This role cannot be edited; create a new role if you need a variation. | | `Agent` | Limited access to conversations, messages, and views. | The `Admin` role is locked and cannot be modified. To grant a subset of admin access, create a new role with only the permissions you need. ## Permissions Permissions follow an `object:action` format (for example `roles:manage`). The sections below list every available permission and what it grants. ### Conversation | Permission | What it grants | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | | `conversations:read` | Open and read an individual conversation, including its participants and search. Required to view any conversation. | | `conversations:write` | Start new conversations. | | `conversations:read_assigned` | List the conversations assigned to the agent. | | `conversations:read_all` | List every conversation in the helpdesk, regardless of who it is assigned to. | | `conversations:read_unassigned` | List conversations that are not yet assigned to anyone. | | `conversations:read_team_inbox` | List the unassigned conversations in the inboxes of the agent's teams. | | `conversations:read_team_all` | List all conversations assigned to the agent's teams. | | `conversations:update_user_assignee` | Assign a conversation to an agent, or remove that assignment. | | `conversations:update_team_assignee` | Assign a conversation to a team, or remove that assignment. | | `conversations:update_priority` | Set a conversation's priority. | | `conversations:update_status` | Change a conversation's status (for example open, resolved, or snoozed). | | `conversations:update_tags` | Add or remove tags on a conversation. | | `messages:read` | Read the messages in a conversation and download transcripts. | | `messages:write` | Reply to and send messages in a conversation. | | `messages:write_as_contact` | Post a message into a conversation on the contact's behalf. | | `view:manage` | Create and manage the agent's own saved conversation views (filters). | ### Admin | Permission | What it grants | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | | `general_settings:manage` | Edit instance-wide settings such as business name, branding, and defaults. | | `notification_settings:manage` | Configure the email notifications that agents receive. | | `status:manage` | Create, rename, and delete conversation statuses. | | `oidc:manage` | Add and edit SSO (OpenID Connect) login providers. | | `tags:manage` | Create, edit, delete, and import tags. | | `macros:manage` | Create and edit macros (reusable sets of replies and actions). | | `users:manage` | Create, edit, and delete agents, and assign their roles. See the warning below. | | `teams:manage` | Create and edit teams and their membership. | | `automations:manage` | Create and edit automation rules that act on conversations. | | `inboxes:manage` | Create, configure, and delete inboxes (email, live chat, WhatsApp), including their channel connections and WhatsApp templates. | | `roles:manage` | Create, edit, and delete roles and the permissions they grant. | | `templates:manage` | Create and edit email templates. | | `reports:manage` | View the reporting dashboards (overview, CSAT, SLA, message volume, tag distribution). | | `business_hours:manage` | Define business-hour schedules and holidays used by SLAs. | | `sla:manage` | Create and edit SLA policies. | | `ai:manage` | Configure the AI provider and prompts. | | `custom_attributes:manage` | Create and edit custom fields for conversations and contacts. | | `activity_logs:manage` | View the activity (audit) log. | | `webhooks:manage` | Create and edit outgoing webhooks. | | `shared_views:manage` | Create and manage saved views shared with other agents. | | `context_links:manage` | Configure the context links shown alongside a conversation. | **`users:manage` is effectively full access.** It lets an agent assign any role, including `Admin`, to any agent, themselves included. So anyone with `users:manage` can grant themselves every other permission. Treat it as equivalent to admin access and give it only to people you fully trust. ### Contact | Permission | What it grants | | ---------------------- | --------------------------------------------------------- | | `contacts:read_all` | List and view every contact. | | `contacts:read` | View an individual contact's profile and search contacts. | | `contacts:write` | Edit a contact's details. | | `contacts:block` | Block or unblock contacts. | | `contact_notes:read` | Read the private notes on a contact. | | `contact_notes:write` | Add private notes to a contact. | | `contact_notes:delete` | Delete private notes on a contact. | ## Creating a role Go to **Admin → Teams → Roles** and click **New role**. Give it a name and an optional description. Check the permissions to grant. Anything left unchecked is denied. Save the role, then assign it to agents from their user settings. An agent's access is the combined permissions of all roles assigned to them.