# Guide to Rubrik Webhooks: Features and Best Practices

Rubrik Security Cloud webhooks push events out of RSC as they happen: a backup fails, an anomaly is detected, an archive or replication job finishes, or a user changes an SLA domain. If you route Rubrik events into an incident tool, a SIEM, a chat channel or your own automation, webhooks are how they get there without polling the RSC API.

This guide covers how RSC webhooks are created, the default and custom payload formats, how to authenticate deliveries that Rubrik does not sign, Rubrik's short retry policy, and the best practices for running a Rubrik receiver in production.

## What are Rubrik webhooks?

An RSC webhook sends events to a URL. Each webhook subscribes to either workload events or audit events, filtered by event type, object type and severity. You create webhooks in the RSC console or with the RSC GraphQL API.

The default payload is PagerDuty Common Event Format (PD-CEF), with a short summary at the top level and the event's details in `custom_details`. You can replace it with a custom JSON template, for example to match what Datadog or another tool expects.

Rubrik does not sign webhook requests. To authenticate them, you configure a credential on the webhook, such as a custom header, that Rubrik sends with each delivery.

## Rubrik webhook features

| Feature | Details |
| --- | --- |
| Configuration | RSC console, or the `createWebhookV2` GraphQL mutation |
| Subscriptions | Workload events or audit events, filtered by event type, object type and severity |
| Payload | PD-CEF by default, or a custom JSON template |
| Event class | Top-level `class` in PD-CEF, title case on the wire (for example `Backup`) |
| Event ID | `custom_details.id`, the activity ID |
| Authentication | No signature. `CUSTOM_HEADER`, `BEARER`, `BASIC`, `OAUTH2` or `URL` |
| Retries | 3, at 1 second, 5 seconds and 10 seconds. Then the event is removed from the queue |
| Auto-disable | After 25 consecutive failures |
| Event history | 90 days in RSC, queryable with GraphQL |

Rubrik does not document the HTTP method, the content type, a response timeout or source IP addresses for webhook deliveries.

## Common events

Webhook subscriptions choose event types from RSC's `EventType` enum, such as `BACKUP`, `ANOMALY`, `ARCHIVE`, `REPLICATION`, `RECOVERY`, `SECURITY_VIOLATION`, `THREAT_HUNT` and `UPGRADE`. The enum has over 70 values.

In a PD-CEF delivery, the event's class arrives in the `class` field in title case. A `BACKUP` subscription produces bodies with `"class": "Backup"`. The specific event is in `custom_details.eventName`, such as `Snapshot.BackupFailed`.

Here is Rubrik's example of a failed backup in the default format:

```json
{
  "summary": "Failed backup of vSphere VM 'sh1-EncryptMe-05-Group1'.",
  "source": "Rubrik Security Cloud",
  "severity": "critical",
  "timestamp": "2024-07-18T06:39:40.46Z",
  "class": "Backup",
  "custom_details": {
    "seriesId": "ccd7a8a5-4c58-4c88-bff9-7bdffddb6099",
    "id": "c2b47274-6323-4025-b307-afed1cfb7574",
    "type": "Event",
    "objectId": "83c4a80a-4a57-5699-b399-651089135586",
    "objectName": "sh1-EncryptMe-05-Group1",
    "objectType": "VmwareVm",
    "status": "Failure",
    "clusterId": "6a271636-9392-4cba-90c5-bdbe227854ab",
    "clusterName": "sh1-PaloAlto",
    "eventName": "Snapshot.BackupFailed",
    "errorId": "Snapshot.VmwareSnapshotError",
    "location": "sh1-paloalto-vcsa.rubrikdemo.com"
  }
}

```

`custom_details.id` identifies this activity, and `custom_details.seriesId` is the activity series ID, which Rubrik suggests for grouping related events.

### Custom templates

A custom template is freeform JSON with variables, for example:

```json
{
  "title": "{{.ActivityType}} - {{.Status}}",
  "message": "{{.Message}}",
  "class": "{{.Class}}",
  "id": "{{.UUID}}",
  "aggregationKey": "{{.ActivitySeriesID}}",
  "timestamp": "{{.Time.UTC.Format \"1970-01-01T00:00:00Z\"}}"
}

```

If you use a template, keep `{{.UUID}}` and `{{.Class}}` in it, so your receiver can still deduplicate and route.

## Setting up Rubrik webhooks

Generate a long random secret for the webhook, for example with `openssl rand -hex 32`. Then create the webhook in the RSC console, or with the API:

```graphql
mutation {
  createWebhookV2(input: {
    payload: {
      name: "events-to-receiver"
      description: "Rubrik events for our receiver"
      url: "https://example.com/webhooks/rubrik"
      subscriptionType: {
        eventSubscription: {
          objectTypes: []
          isSubscribedToAllObjectTypes: true
          eventTypes: [BACKUP, ANOMALY]
          severities: [SEVERITY_CRITICAL]
        }
      }
      providerType: CUSTOM
      authInfo: {
        authType: CUSTOM_HEADER
        customHeaders: {
          headerKey: "Authorization"
          headerValue: "Bearer YOUR_GENERATED_SECRET"
        }
      }
    }
  }) {
    webhook { name id status }
    errorInfo { errorMessage statusCode }
  }
}

```

Use `CUSTOM_HEADER` authentication. It is the only mode whose wire form Rubrik documents: the header name and value you set are sent as they are. Rubrik also lists `BEARER`, `BASIC`, `OAUTH2` and `URL`, but doesn't document what they look like on the request.

To list your webhooks and their last error, query `allWebhooksV2`.

## Securing Rubrik webhooks

With no signature, verification means checking the header you configured. Compare the whole value with the one you stored, in constant time, and fail closed if your secret isn't configured.

```javascript
const crypto = require('crypto');
const express = require('express');

// The headerKey you set on the Rubrik webhook. Node lowercases incoming header names.
const SECRET_HEADER = (process.env.RUBRIK_WEBHOOK_HEADER || 'authorization').toLowerCase();

function verifyRubrikHeader(headers, expected) {
  const received = headers[SECRET_HEADER];
  if (typeof received !== 'string' || !expected) return false;

  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.post('/webhooks/rubrik', express.raw({ type: '*/*' }), (req, res) => {
  // The full headerValue, for example "Bearer <secret>".
  const secret = process.env.RUBRIK_WEBHOOK_SECRET;
  if (!secret) return res.sendStatus(500); // fail closed

  if (!verifyRubrikHeader(req.headers, secret)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  // Deduplicate on event.custom_details.id, route on event.class, then process async.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import hmac

def verify_rubrik_header(headers, expected: str, header_name: str = "authorization") -> bool:
    received = headers.get(header_name)
    if not received or not expected:
        return False
    return hmac.compare_digest(received.encode("utf-8"), expected.encode("utf-8"))

```

This check proves the caller knows your secret. It doesn't prove the body is unchanged, and the header is the same on every delivery, so a captured request could be sent again.

> **Make Rubrik webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) checks your Rubrik header before anything reaches your code, stores every event durably, and retries far beyond Rubrik's three quick attempts.

## Rubrik webhook limitations and pain points

### Three retries in 16 seconds

**The Problem:** A brief outage or a slow deploy on your side means lost backup failures and security alerts.

**Why It Happens:** Rubrik retries a failed delivery 3 times, after 1, 5 and 10 seconds. After that, "the event will be removed from the queue".

**Workarounds:**

* Return a `2xx` as soon as the header checks out, and do the work afterwards.
* After an outage, query `activitySeriesConnection` or `userAuditConnection` for the window you missed. RSC keeps 90 days of events.

**How Hookdeck Can Help:** Hookdeck accepts each event as soon as it arrives and stores it, so Rubrik's first attempt succeeds even while your service is down. You then set your own [retry](/docs/retries) policy and replay anything that failed.

### A failing endpoint gets switched off

**The Problem:** After a longer outage, Rubrik stops sending to the webhook and nothing arrives until someone notices.

**Why It Happens:** A webhook is auto-disabled after 25 consecutive failures.

**Workarounds:**

* Check webhook status and `lastFailedErrorInfo` with `allWebhooksV2`, and alert when a webhook is disabled.
* Re-enable the webhook once your receiver is healthy.

**How Hookdeck Can Help:** Hookdeck acknowledges deliveries even while your service is down, so your outage doesn't count against the webhook. [Issues](/docs/issues) alert you when delivery to your service starts failing.

### No signature, only a static credential

**The Problem:** A webhook without authentication accepts anything posted to its URL, and a captured request carries a credential that never changes.

**Why It Happens:** Rubrik signs nothing. Its authentication modes send a credential to your receiver, and only the custom header mode is documented on the wire.

**Workarounds:**

* Use `CUSTOM_HEADER` with a long random value, over HTTPS.
* Never log the header value.
* To rotate, accept both values for a short time, update the webhook, then remove the old value.

**How Hookdeck Can Help:** Hookdeck's Rubrik source type checks a header for you. Enter the header name and the exact value, including any `Bearer ` prefix, and Hookdeck rejects requests without it before they reach your service.

### The class in the body doesn't match the subscription name

**The Problem:** A router that matches `BACKUP` never sees a backup event.

**Why It Happens:** Subscriptions use `EventType` enum values such as `BACKUP`, but PD-CEF bodies carry the class in title case, such as `Backup`.

**Workarounds:**

* Route on the title-case `class` value, or compare case-insensitively.
* Use `custom_details.eventName` when you need the specific event.

**How Hookdeck Can Help:** Hookdeck [filters](/docs/filters) can route on `class`, `severity` or `custom_details.status`, so failures go to your incident tool and successes go to reporting.

## Best practices

### Deduplicate on the activity ID

Retries resend the same event. Store `custom_details.id` (or `{{.UUID}}` in a custom template) and skip repeats. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Keep the default format unless you need a template

PD-CEF already includes the IDs you need. If a downstream tool needs a different shape, consider transforming events on the way rather than changing the template for every consumer. Hookdeck [transformations](/docs/transformations) can do that.

### Subscribe narrowly

Filter by event type, object type and severity so your receiver handles only what it acts on.

### Acknowledge fast and process asynchronously

With retries that end after about 16 seconds, return `200` straight away and queue the work. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Rubrik Security Cloud sends workload and audit events in PD-CEF or a custom template, with no signature and a retry policy that gives up after three attempts in about 16 seconds. Protect each webhook with a `CUSTOM_HEADER` credential, compare it in full, route on the title-case `class`, deduplicate on `custom_details.id`, and query the RSC API to fill any gaps.

[Hookdeck Event Gateway](https://hookdeck.com) verifies your header and stops requests that fail it, stores every event before your service sees it, and retries on your terms, so a short outage doesn't lose a backup failure or a security alert.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Rubrik webhooks reliably in minutes.