## Webhooks

A webhook sends issue data as an HTTP POST request with a JSON body to an address you choose. You can use it to:

- notify company channels — **Slack**, **Google Chat**, **Microsoft Teams**,
- pass events to your own system (ERP, CRM, shop, automations).

Webhooks are configured under **System → Webhooks**.

{% callout title="Requirements" %}
Webhooks are available in the **Business** plan. Managing them requires permissions from the *Webhooks* group (browse, view, add, edit, delete), granted by the account administrator.
{% /callout %}

## How it works

Sending is made of three parts:

1. **Webhook** — where to send: address, kind and authentication. One webhook (e.g. a Slack channel) can be used by many rules.
2. **Webhook template** — what to send: a JSON body with issue data, created in [Templates](/config/templates#webhook-template).
3. **Business rule** — when to send: the **Sending webhook** action links a webhook with a template and runs on the chosen event ([Business rules](/config/business-rules#sending-webhook)).

This way the same channel can receive different messages — e.g. "new issue" from one rule and "step changed" from another.

## Adding a webhook

1. Go to **System → Webhooks** and click **New webhook**.
2. Enter a **Name** and choose the **Kind**.
3. Paste the **URL** provided by the receiver (`https` required).
4. For the *Generic* kind, optionally choose **Authentication**.
5. Save. For a generic webhook you will see the **signing secret** — copy it right away, it is shown only once.

### Webhook kinds

| Kind | Used for | Address |
|---|---|---|
| **Generic (JSON + signature)** | Your own system or any service accepting JSON | Any public `https` address |
| **Slack** | A Slack channel (Incoming Webhook) | `https://hooks.slack.com/...` |
| **Google Chat** | A Google Chat space (incoming webhook) | `https://chat.googleapis.com/...` |
| **Microsoft Teams** | A Teams channel (a *Workflows* flow that accepts a webhook request) | Address generated by Teams |

Slack, Google Chat and Teams webhook addresses contain a secret token. That is why Reklamator does not show the full address after saving — only its beginning (e.g. `https://hooks.slack.com/…`). When editing, leave the address field empty to keep it unchanged.

Internal addresses (e.g. `localhost`, private network addresses) are rejected — the webhook must be reachable from the internet.

### Authentication (generic webhook)

| Authentication | What Reklamator sends |
|---|---|
| **None** | No authentication header |
| **Basic** | `Authorization: Basic …` with the given user and password |
| **Bearer** | `Authorization: Bearer <token>` |
| **Custom header** | A header with the given name and value, e.g. `X-Api-Key` |

A custom header cannot override technical headers (`Host`, `Content-Type`, `Content-Length`, `Connection`, `User-Agent`, `Transfer-Encoding` or `X-Reklamator-*` headers). Passwords and tokens are stored encrypted and are never shown after saving.

Slack, Google Chat and Teams use no authentication or signature — the token is part of the address.

## Message template

The body sent by a webhook is a template of type **Webhook**. When creating the template, choose **Starter Templates** — there are ready-made patterns for each kind (generic JSON, Slack, Google Chat, Teams).

Example for Google Chat or Slack:

```json
{"text": {{ ("New issue " ~ issue_internal_number ~ " (" ~ issue_type_name ~ ")")|json }}}
```

The rules for writing a webhook template are described in [Templates → Webhook template](/config/templates#webhook-template).

## Testing a webhook

Choose **Send test** from the row menu on the list. You can send:

- **sample data** — without choosing a template, to check the address and authentication,
- **a template on a chosen issue** — choose a webhook template and enter an issue number; you will see exactly what the rule will send.

The result is shown immediately: status, receiver response code, time and response body. If the template does not produce valid JSON, the test reports it right away. Tests do not affect the webhook failure counter.

## Deliveries and retries

Each rule run is a **delivery**. The webhook delivery list is available from the row menu (**Deliveries**) — it shows the status, event, number of attempts, last response code and the data sent.

| Status | Meaning |
|---|---|
| Pending | Delivery queued or waiting for a retry |
| Delivered | The receiver responded with 2xx |
| Failed | All attempts failed or the receiver rejected the request |
| Cancelled | The webhook was disabled before sending |

**Retries.** If the receiver is temporarily unavailable (no connection, 10 s timeout exceeded, code 408, 425, 429 or 5xx), Reklamator retries with growing intervals: after about 5 s, 25 s, 2 min and 10 min — up to 5 attempts in total. Other 4xx codes and redirects (3xx) end the delivery without retrying, as they usually mean a configuration error.

**Redeliver** on the delivery list sends the same data once more as a new delivery — e.g. after a fix on the receiver's side. It does not work for a disabled webhook.

Deliveries are kept for 30 days.

### Automatic disabling

After **20 consecutive** failed deliveries the webhook disables itself and is marked **Disabled automatically**. The system log then gets a `WEBHOOK_AUTO_DISABLED` entry. After fixing the receiver, enable the webhook in its edit form — the failure counter resets.

## System log

Every sending attempt is written to the [system log](/logs/system) in the **WEBHOOK** module: successful as **INFO**, failed as **ERROR**. The entry contains the webhook name, event, attempt number, response code and time, and a fragment of the receiver's response.

Errors detected before sending are logged too, e.g. `WEBHOOK_INVALID_PAYLOAD` — the template did not produce valid JSON for the given issue.

## For integrators (generic webhook)

The request sent by Reklamator:

| Element | Value |
|---|---|
| Method | `POST` |
| `Content-Type` | `application/json; charset=utf-8` |
| `User-Agent` | `Reklamator-Webhook/1.0` |
| `X-Reklamator-Event` | Rule event, e.g. `ISSUE_CREATE`, `ISSUE_CHANGE_STEP` (`TEST` for a test) |
| `X-Reklamator-Delivery` | Delivery ID — the same on every retry; use it to drop duplicates |
| `X-Reklamator-Timestamp` | Attempt send time (seconds since 1970-01-01 UTC) |
| `X-Reklamator-Signature` | `v1=<HMAC-SHA256>` — only when the webhook has a signing secret |
| Body | The rendered webhook template (max. 256 KB) |

The signature is an HMAC-SHA256 of the text `<timestamp>.<body>` using the webhook secret, hex-encoded. Verification on the receiver side (Python):

```python
import hashlib, hmac, time

def is_valid(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False
    expected = 'v1=' + hmac.new(secret.encode(), f'{timestamp}.'.encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
```

Compute the signature from the **raw** request body, before parsing JSON. Respond with 2xx as quickly as possible — move long processing to your own queue.

The secret can be replaced from the row menu (**Generate new secret**). The old secret stops working immediately, so update it on the receiver's side.

---

## FAQ

### Can I send to several channels at once?
Yes. Add a separate webhook for each channel and add several **Sending webhook** actions to the rule.

### Can one template be used by many webhooks?
Yes. The template and the webhook are independent — you choose them separately in the rule action.

### Why can't I see the full webhook address?
Slack, Google Chat and Teams addresses contain a secret token, so they are stored encrypted and not displayed. To change the address, enter a new one in the webhook edit form.

### The webhook doesn't arrive — where do I look?
Check the webhook's **Deliveries** list and the [system log](/logs/system) (WEBHOOK module). No delivery with a `WEBHOOK_INVALID_PAYLOAD` error means the template is wrong — check it with **Send test** on the same issue.

### Can I delete a webhook used in a rule?
No. Remove it from the business rule action first. You can also disable a webhook without deleting it.

### Can the receiver get the same message twice?
Rarely — e.g. when the receiver accepted the request but the response did not reach Reklamator. Every retry carries the same `X-Reklamator-Delivery` header, which the receiver can use to drop the duplicate.
