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
Navigate to webhooks
Go to Admin → Integrations → Webhooks in your libredesk dashboard
Create webhook
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
Test webhook
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=<signature>.
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)
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return signature === `sha256=${expectedSignature}`;
}
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/<version> |
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 for details and how to enable it.
Events
Conversation events
conversation.created
Triggered when a new conversation is created.
{
"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": "[email protected]",
"type": "contact"
},
"custom_attributes": {},
"tags": []
}
}
conversation.status_changed
Triggered when a conversation's status is updated.
{
"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": "[email protected]",
"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": "[email protected]",
"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
}
}
}
conversation.assigned
Triggered when a conversation is assigned to a user.
{
"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": "[email protected]",
"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": "[email protected]",
"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
}
}
}
conversation.unassigned
Triggered when a conversation is unassigned from a user.
{
"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": "[email protected]",
"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": "[email protected]",
"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
}
}
}
conversation.tags_changed
Triggered when tags are added or removed from a conversation.
{
"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": "[email protected]",
"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": "[email protected]",
"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
message.created
Triggered when a new message is created in a conversation.
{
"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": "<p>Hello! How can I help you today?</p>",
"text_content": "Hello! How can I help you today?",
"content_type": "html",
"private": false,
"sender_id": 789,
"sender_type": "agent",
"attachments": []
}
}
message.updated
Triggered when an existing message is updated.
{
"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": "<p>Hello! How can I help you today? (Updated)</p>",
"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:
Webhook.site
Generate a temporary URL to inspect webhook payloads
RequestBin
Collect and inspect HTTP requests
Example integration
An Express server that verifies the signature, replies immediately, and processes the event afterwards:
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');
});
