Get real-time notifications when events happen in Ruut - conversations created, messages sent, contacts updated and more - delivered straight to your endpoint

What Are Webhooks?
Instead of constantly asking Ruut "anything new?", a webhook lets Ruut tell you the moment something happens. When a customer sends a message, when a conversation is resolved, when a new contact is created — Ruut sends an HTTP POST to your URL with the event data.
This means you can:
-
Sync data to your CRM, database, or analytics platform in real time
-
Trigger automations in Zapier, n8n, Make, or your own backend
-
Build custom workflows that react instantly to customer interactions
What You'll Need
Before setting up a webhook:
-
A URL that accepts HTTP POST requests — This is where Ruut will send event data. It must be publicly accessible and respond with a 2xx status code.
-
Administrator access — Only account admins can create and manage webhooks.
Setting Up a Webhook
::::steps
:::step{title="Open Integrations"}
From your Ruut dashboard, go to Settings → Integrations and find Webhook.
:::
:::step{title="Click Add Webhook"}
Click the Add Webhook button to open the configuration form.
:::
:::step{title="Enter Your Endpoint URL"}
Paste the URL where you want to receive webhook payloads. This must be a valid http:// or https:// URL.
:::callout{type="tip" title="Use HTTPS"}
We recommend using HTTPS endpoints. Your endpoint must return a 2xx response within 5 seconds.
:::
:::
:::step{title="Give It a Name (Optional)"}
Add a descriptive name so you can identify this webhook later — for example, "CRM Sync" or "Analytics Pipeline".
:::
:::step{title="Select Events"}
Choose which events you want to receive. You can subscribe to multiple events on the same webhook:
| Event | When It Fires |
|---|---|
conversation_created |
A new conversation is created |
conversation_status_changed |
A conversation is opened, resolved, or snoozed |
conversation_updated |
Conversation attributes are updated |
message_created |
A new message is sent in a conversation |
message_updated |
An existing message is edited |
contact_created |
A new contact is created |
contact_updated |
A contact's details are updated |
webwidget_triggered |
The chat widget is opened by a visitor |
inbox_created |
A new inbox is created |
inbox_updated |
An inbox's settings are changed |
conversation_typing_on |
Someone starts typing in a conversation |
conversation_typing_off |
Someone stops typing |
:::
:::step{title="Save"}
Click Create to activate your webhook. Ruut will immediately start sending events to your endpoint.
:::
::::
Verifying Webhook Signatures
:::callout{type="warning" title="Always verify signatures"}
Without verification, anyone could send fake payloads to your endpoint. Always validate the signature before processing a webhook.
:::
Every webhook payload is signed with an HMAC-SHA256 signature using a secret key that's auto-generated when you create the webhook. You can view and copy this secret from the webhook form in your dashboard.
How Verification Works
Each request includes three headers:
| Header | Description |
|---|---|
X-Ruut-Signature |
The HMAC-SHA256 signature of the payload |
X-Ruut-Timestamp |
The Unix timestamp when the webhook was sent |
X-Ruut-Delivery |
A unique ID for this delivery (for idempotency) |
To verify a payload:
-
Read the
X-Ruut-Timestampheader and the raw request body -
Compute the HMAC:
HMAC-SHA256(your_secret, "#{timestamp}.#{body}") -
Compare your result with the
X-Ruut-Signatureheader (ignoring thesha256=prefix) -
If they match, the payload is authentic
::::tabs
:::tab{title="Ruby"}
def verify_ruut_signature(secret, timestamp, body, signature)
expected = "sha256=" + OpenSSL::HMAC.hexdigest(
'SHA256',
secret,
"#{timestamp}.#{body}"
)
ActiveSupport::SecurityUtils.secure_compare(expected, signature)
end
:::
:::tab{title="Python"}
import hmac, hashlib
def verify_ruut_signature(secret, timestamp, body, signature):
expected = "sha256=" + hmac.new(
secret.encode(), f"{timestamp}.{body}".encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
:::
:::tab{title="Node.js"}
const crypto = require('crypto');
function verifyRuutSignature(secret, timestamp, body, signature) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
:::
::::
:::callout{type="tip" title="Replay protection"}
To prevent replay attacks, check that the X-Ruut-Timestamp is within an acceptable window (e.g., 5 minutes) of the current time.
:::
Payload Structure
Every webhook sends a JSON payload with this structure:
{
"event": "message_created",
"timestamp": "2026-09-04T12:00:00Z",
"data": {
"id": 12345,
"conversation": {
"id": 678,
"display_id": 42,
"status": "open"
},
"contact": {
"id": 910,
"name": "Jane Doe",
"email": "[email protected]"
},
"message": {
"id": 11213,
"content": "Hi, I need help with my order",
"message_type": "incoming",
"created_at": "2026-09-04T12:00:00Z"
}
}
}
The data object varies depending on the event type, but always includes the relevant IDs and attributes.
Handling Responses
Your endpoint should:
-
Return a 2xx status code (e.g.,
200 OK) to acknowledge receipt -
Respond within 5 seconds — otherwise Ruut considers it a failure
-
Process the payload asynchronously if your logic takes longer
:::callout{type="warning" title="Don't block on slow operations"}
If your webhook handler needs to do heavy work (writing to a database, calling external APIs), accept the payload immediately, return 200, and process it in a background job.
:::
Retries
If your endpoint returns a non-2xx status or times out, Ruut will not retry the delivery. Ensure your endpoint is reliable and always returns a success response quickly.
For critical workflows, consider:
-
Using a queue-based handler that acknowledges immediately and processes later
-
Monitoring your endpoint for failures
-
Logging all incoming webhook IDs (
X-Ruut-Delivery) for debugging
Frequently Asked Questions
::::accordion
:::accordion-item{title="Can I have multiple webhooks?"}
Yes. You can create multiple webhooks, each with its own URL, name, and event subscriptions. This is useful if you want different systems to receive different events.
:::
:::accordion-item{title="Can I subscribe to the same event on multiple webhooks?"}
Yes. The same event can be sent to as many webhooks as you configure.
:::
:::accordion-item{title="What happens if my endpoint is down?"}
The webhook delivery will fail. Ruut does not retry failed deliveries, so your endpoint should be highly available. Use a queue-based handler for critical workflows.
:::
:::accordion-item{title="Is there a rate limit?"}
Webhooks are delivered in real time as events occur. If you need to throttle or batch, implement that on your receiving end.
:::
:::accordion-item{title="Can I see delivery history?"}
Currently, webhook delivery logs are not exposed in the dashboard. Use your own endpoint logs and the X-Ruut-Deliveryheader to track individual deliveries.
:::
:::accordion-item{title="How do I rotate my webhook secret?"}
You can regenerate the secret from the webhook form in your dashboard. Update your verification logic with the new secret immediately — the old secret will stop working after regeneration.
:::
::::
Troubleshooting
| Problem | What to Try |
|---|---|
| Not receiving events | Check that you've subscribed to the right events and your URL is publicly accessible |
| Getting 502 or timeout errors | Your endpoint is too slow — respond within 5 seconds |
| Signature verification failing | Make sure you're using the raw request body (not parsed JSON) and the correct secret |
| Duplicate deliveries | Use the X-Ruut-Delivery header to deduplicate — the same ID won't be sent twice |
| Webhook shows as disabled | Your endpoint returned errors repeatedly — check your logs and re-enable it |
:::callout{type="info" title="Need help?"}
If you're stuck, reach out to our support team from the dashboard or email us at [email protected].
:::