> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runrally.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Get an HTTP call when something happens in Rally: content published, a commission posted, a payout paid.

Register a URL and Rally sends it a POST whenever one of the events you chose happens. Set endpoints up under **Settings → Webhooks**, or with the [REST API](/developers/api) (`POST /v1/webhooks` with `url` and `events`, scope `webhooks:manage`).

When you create an endpoint, Rally shows its **signing secret** once. Store it; you need it to verify deliveries.

## Events

| Area | Events |
| - | - |
| Applications | `application.submitted`, `application.approved`, `application.declined` |
| Creators | `creator.activated`, `creator.paused`, `onboarding.completed`, `candidate.invited` |
| Content | `content.planned`, `content.published`, `content.approved`, `analytics.verified` |
| Money | `reward.posted`, `commission.posted`, `commission.voided`, `payout.requested`, `payout.paid`, `payout.failed`, `bounty.completed`, `benefit.granted` |

## The delivery

```json theme={null}
{
  "event": "content.published",
  "tenant_id": "…",
  "occurred_at": "2026-10-16T18:04:11Z",
  "data": { … }
}
```

`data` is the record the event is about, in the same shape the app and API use. Two headers come with it:

| Header | Value |
| - | - |
| `X-CreatorOps-Delivery` | A unique id for this delivery. Use it to ignore duplicates. |
| `X-CreatorOps-Signature` | `t=<unix seconds>,v1=<hex HMAC-SHA256>` over `<t>.<raw body>` with your signing secret. |

## Verifying a delivery

Compute the HMAC over the timestamp, a dot and the raw request body, and compare it to `v1`. Reject deliveries whose timestamp is more than a few minutes old.

<CodeGroup>
  ```js Node.js theme={null}
  import { createHmac, timingSafeEqual } from "node:crypto";

  export function verify(secret, header, rawBody) {
    const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
    const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
    const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
    return fresh && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
  }
  ```

  ```python Python theme={null}
  import hmac, hashlib, time

  def verify(secret: str, header: str, raw_body: bytes) -> bool:
      parts = dict(kv.split("=", 1) for kv in header.split(","))
      expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
      fresh = abs(time.time() - int(parts["t"])) < 300
      return fresh and hmac.compare_digest(expected, parts["v1"])
  ```
</CodeGroup>

Verify against the raw body exactly as received; re-serialising the JSON changes the bytes.

## Retries

Respond with any `2xx` within a few seconds. Anything else, or a timeout, is retried with growing gaps (about 1, 2, 4, 8 and 16 minutes) for up to six attempts, then marked failed. Deliveries and their status are listed per endpoint under **Settings → Webhooks**. Handlers should be idempotent: the same delivery id can arrive more than once.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.