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

![](https://app.ruut.chat/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBMVkvQnc9PSIsImV4cCI6bnVsbCwicHVyIjoiYmxvYl9pZCJ9fQ==--18fdf6e2087940ea95e457368654f113e66d8fda/image.png)

### 

---

## 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:

1. Read the `X-Ruut-Timestamp` header and the raw request body

2. Compute the HMAC: `HMAC-SHA256(your_secret, "#{timestamp}.#{body}")`

3. Compare your result with the `X-Ruut-Signature` header (ignoring the `sha256=` prefix)

4. 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": "jane@example.com"
    },
    "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-Delivery`header 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 support@ruut.chat.

:::


