Receiving Real-Time Events With Ruut Webhooks

Mona

Mona

Last updated on Sep 4, 2026

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:

  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": "[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].

:::