---
title: "Webhooks"
space: "Raven"
url: "https://docs.frappe.io/raven/agents-and-webhooks/webhooks"
updated: "2026-10-19"
---

A webhook sends a request to another system when something happens in Raven, such as a new message or a new channel. Set them up in **Settings > Webhooks**.

To post into Raven from another system, use an [incoming webhook](wiki:40bd4f13d16c) instead.

:::note
You need the System Manager role to create, change or delete webhooks.
:::

## Create a webhook

1. Open **Settings > Webhooks**.
2. Click **Create**.
3. Fill in the **General** tab.
4. If you like, add a condition on the **Conditions** tab.
5. On the **Data** tab, pick the fields to send.
6. If the other system needs them, add headers on the **Headers** tab.
7. Click **Create**.

<picture>
  <source srcset="/files/raven-dark-bots-webhook-create.webp" media="(prefers-color-scheme: dark)">
  <img src="/files/raven-light-bots-webhook-create.webp" alt="A new webhook in Settings">
</picture>

Set up the **Data** and **Headers** tabs before you click **Create**.

## General

| Field | What it does |
| :--- | :--- |
| Name | A name for the webhook. It can't be changed after you create the webhook. |
| Request URL | The URL Raven sends the request to. Up to 140 characters. |
| Request Timeout | How many seconds Raven waits for a response. The default is 5. |
| Trigger Event | The event that sends the request. It can't be changed after you create the webhook. |
| Enable Security? | Signs each request with a secret. See **Check the signature** below. |
| Webhook Secret | The secret used to sign requests. Shows when **Enable Security?** is on. |

## Trigger events

| Trigger Event | Sent when |
| :--- | :--- |
| Message Sent | A message is posted. |
| Message Edited | A message is edited. |
| Message Deleted | A message is deleted. |
| Message Reacted On | Someone adds a reaction to a message. |
| Channel Created | A channel is created. |
| Channel Deleted | A channel is deleted. |
| User Added | A user is added to Raven. Creating an agent counts too. |
| User Deleted | A Raven user is deleted. |

## Conditions

Turn on **Trigger this webhook based on a condition** to send the request only for some events. Then pick a field in **Trigger On**.

<picture>
  <source srcset="/files/raven-dark-bots-webhook-conditions.webp" media="(prefers-color-scheme: dark)">
  <img src="/files/raven-light-bots-webhook-conditions.webp" alt="The Conditions tab">
</picture>

| Trigger On | What it checks | Works with |
| :--- | :--- | :--- |
| Channel | The message was posted in this channel. | Message Sent, Message Edited, Message Deleted |
| User | The message or reaction was sent by this user. | Message Sent, Message Edited, Message Deleted, Message Reacted On |
| Channel Type | The channel is Public, Private, Open, a direct message or a self message. | Channel Created, Channel Deleted |
| Custom | A Python expression you write. The request is sent when it is true. | Every trigger event |

In a custom condition, `doc` is the record that changed. For example:

```python
doc.channel_id == "lumen-cycles-support"
doc.owner == "maya@lumencycles.com"
```

## Data

Click **Add** and pick a field to send. Each field is sent under its field name, shown in the **Key** column. The list of fields depends on the trigger event.

Click the info button on a row to see what the field holds. Click **Preview** and pick an example to see the request body.

## Headers

Click **Add** to send a header with each request. Enter a **Key** and a **Value**. A header with no value is not sent.

Raven does not set a `Content-Type` header. If the other system needs one, add `Content-Type` with the value `application/json`.

## The request

Raven sends a `POST` request. The body is a JSON object with the fields from the **Data** tab. For **Message Sent** with the fields `channel_id`, `owner` and `text`, the body looks like this:

```json
{
 "channel_id": "lumen-cycles-support",
 "owner": "maya@lumencycles.com",
 "text": "<p>The rear hub on order 4127 is back in stock.</p>"
}
```

Message text is HTML.

If the request fails, Raven tries again up to 3 times: after 5 minutes, 30 minutes and 2 hours. Every request is logged in Desk, under **Webhook Request Log**.

## Check the signature

With **Enable Security?** on, each request has an `X-Frappe-Webhook-Signature` header. Raven computes it like this:

1. Take the request body, exactly as sent, as UTF-8 bytes.
2. Compute its HMAC-SHA256, with the webhook secret as the key.
3. Encode the result in base64.

To check a request, do the same with the raw body you received and compare the two values. Do not parse the JSON and encode it again first, because the bytes may change.

This receiver uses Flask. It checks the signature, then reads the fields:

```python
import base64
import hashlib
import hmac
import json

from flask import Flask, abort, request

app = Flask(__name__)
WEBHOOK_SECRET = "the secret you entered in Raven"


@app.post("/raven")
def raven_webhook():
	raw_body = request.get_data()
	digest = hmac.new(WEBHOOK_SECRET.encode("utf8"), raw_body, hashlib.sha256).digest()
	expected = base64.b64encode(digest).decode()
	received = request.headers.get("X-Frappe-Webhook-Signature", "")

	if not hmac.compare_digest(expected, received):
		abort(401)

	# Raven sends no Content-Type header, so parse the body yourself.
	data = json.loads(raw_body)
	print(data.get("channel_id"), data.get("text"))
	return "", 204
```

Reply with a 2xx status. A 4xx or 5xx status, or no reply within the timeout, counts as a failure, and Raven tries again later.

## Delete a webhook

1. Open **Settings > Webhooks** and click the webhook.
2. Click **⋮** next to **Save**.
3. Click **Delete**, then confirm.
