[rondar](/)

[Product](/)[Pricing](/pricing)[Demo](/demo)[Docs](/docs)[Contact](/contact)

[Sign in](/access#access-sign-in)[Request an invite](/access)

[Product](/)[Pricing](/pricing)[Demo](/demo)[Docs](/docs)[Contact](/contact)[Sign in](/access#access-sign-in)

Docs

**Help article: Webhooks**

Search documentation

_Field: `search` (search)_

No matching articles.

Start

[Your first session](/docs/getting-started)[Connect a repository](/docs/connecting-a-repo)[Connect a coding agent](/docs/connecting-agents)

Work

[Working with sessions](/docs/sessions)[Plan a change before you build it](/docs/plan-mode)[Preview the running result](/docs/live-previews)[Make your app ready for previews](/docs/preview-ready-app)[From session to pull request](/docs/pull-requests)[Get a second agent to review a pull request](/docs/independent-reviews)[Todos and saved workflows](/docs/todos-and-workflows)

Ship

[Ship a release](/docs/releases)[Set up GitHub Actions deploy workflows](/docs/github-actions-deploy-workflows)

Configure

[Agent instructions](/docs/agent-instructions)[Project settings](/docs/project-settings)[Give sessions SSH access to a server](/docs/ssh-servers)[Notifications](/docs/notifications)

Integrate

[Webhooks](/docs/webhooks)[Connect an external agent with MCP](/docs/mcp-and-api)

Reference

[When something needs attention](/docs/troubleshooting)[Self-host setup](/docs/self-host-setup)[Frequently asked questions](/docs/faq)

Documentation

# Webhooks

Rondar sends a signed HTTP request to your own address when a session finishes, a pull request opens or a release ships.

Updated October 13, 2026

1

## Know the events

Use a webhook to connect Rondar to your own tools: post a message to a team chat, update a ticket, or start a job.

| Event | When Rondar sends it | Fields in `data` |
| --- | --- | --- |
| `session.completed` | A session finished its work | `session_id`, `title`, `status`, `project_id` |
| `session.failed` | A session failed | `session_id`, `title`, `status`, `project_id` |
| `pr.opened` | Rondar opened the pull request of a session | `session_id`, `title`, `pr_url`, `pr_number` |
| `release.shipped` | A deployment of a release completed | `deployment_id`, `environment`, `session_id`, `project_id`, `url` |

2

## Add an endpoint

Open **Settings → Webhooks** and select **Add webhook**.

1. Give the **Payload URL**. It must be a public HTTPS address.

2. Give a description, if you want one.

3. Select one event or more.

4. Select **Save endpoint**.

Rondar shows the signing secret one time. It starts with `whsec_`. Copy it now and keep it with your other secrets: Rondar stores it encrypted and does not show it again.

3

## Read the request

Each delivery is a `POST` request with a JSON body:

```
{
"event": "pr.opened",
"timestamp": "2026-10-13T09:24:11Z",
"data": {
"session_id": 1842,
"title": "Add an allergen filter to the breads page",
"pr_url": "https://github.com/example/bakery/pull/12",
"pr_number": 12
}
}
```

The request has these headers:

| Header | Value |
| --- | --- |
| `Content-Type` | `application/json` |
| `User-Agent` | `Rondar-Webhooks/1.0` |
| `X-Rondar-Event` | The name of the event |
| `X-Rondar-Delivery` | The identifier of the delivery |
| `X-Rondar-Signature` | `t=<unix time>,v1=<signature>` |

4

## Verify the signature

Anybody who knows your address can send a request to it. Verify the signature before you use a request.

The signature is the HMAC-SHA256 of the text `<t>.<body>`, with your signing secret as the key, written as lower-case hexadecimal. `<t>` is the time from the header and `<body>` is the body exactly as you received it.

Use the bytes of the body as they arrived. If your framework parses the JSON and you write it again, the signature does not match.

```
// Node.js, with Express
import crypto from "node:crypto";
import express from "express";

const app = express();
const secret = process.env.RONDAR_WEBHOOK_SECRET;

app.post("/rondar", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("X-Rondar-Signature") || "";
const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.`)
.update(req.body)
.digest("hex");

const valid =
parts.v1 &&
parts.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));

// Refuse a request that is older than five minutes.
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;

if (!valid || !fresh) return res.status(400).end();

const { event, data } = JSON.parse(req.body);
console.log(event, data);
res.status(204).end();
});

app.listen(process.env.PORT || 3000);
```

```
# Python, with Flask
import hashlib
import hmac
import os
import time

from flask import Flask, abort, request

app = Flask(__name__)
secret = os.environ["RONDAR_WEBHOOK_SECRET"].encode()

@app.post("/rondar")
def rondar():
header = request.headers.get("X-Rondar-Signature", "")
parts = dict(part.split("=", 1) for part in header.split(",") if "=" in part)
signed = parts.get("t", "").encode() + b"." + request.get_data()
expected = hmac.new(secret, signed, hashlib.sha256).hexdigest()

if not hmac.compare_digest(parts.get("v1", ""), expected):
abort(400)

# Refuse a request that is older than five minutes.
if abs(time.time() - int(parts["t"])) > 300:
abort(400)

payload = request.get_json()
print(payload["event"], payload["data"])
return "", 204
```

5

## Answer fast

Answer with a status from 200 to 299 in less than 10 seconds. Do the long work after the answer, for example in a queue.

If the answer is an error or does not come, Rondar tries again after 1 second and after 5 seconds. After three tries the delivery is failed, and Rondar does not try again by itself.

A delivery can arrive more than one time. `X-Rondar-Delivery` is the same for each try of one delivery, so use it to ignore a delivery that you already handled.

6

## Test and inspect deliveries

- **Send test** sends an `endpoint.test` event to the endpoint.

- The **Delivery log** lists each delivery with its status. Open one to see the request and the answer of your server.

- **Retry this delivery** sends a failed delivery again, with the same identifier and the same body.

7

## Change or stop an endpoint

**Rotate secret** makes a new signing secret; change it in your server at the same time. **Disable** stops the deliveries and keeps the endpoint. **Delete** removes it.

A project or a session with its notifications turned off sends no webhook for its events.

[Request an invite →](/access)

## On this page

[Know the events](#step-1)[Add an endpoint](#step-2)[Read the request](#step-3)[Verify the signature](#step-4)[Answer fast](#step-5)[Test and inspect deliveries](#step-6)[Change or stop an endpoint](#step-7)

[rondar](/)© 2026 Rondar

[Docs](/docs)[Pricing](/pricing)[Demo](/demo)[Rondar vs Amp](/compare/amp)[Rondar vs T3 Code](/compare/t3-code)[Rondar vs OpenHands](/compare/openhands)[Contact](/contact)[Security](/security)
