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

# Webhooki: zdarzenia do Twojego systemu

> Podpisane wiadomości po rozmowie, SMS-ie, kampanii albo przy niskim saldzie, wysyłane na Twój adres.

Webhook to adres w Twoim systemie (backend, CRM, n8n, Make, Zapier), na który
Brosbots wysyła `POST` z JSON-em, gdy coś się wydarzy: skończy się rozmowa,
przyjdzie SMS, kampania obdzwoni listę. Każda wiadomość jest podpisana Twoim
kluczem, więc możesz sprawdzić, że przyszła od nas.

## Zakładanie webhooka

**Integracje** → zakładka **Własne** → kafelek **Powiadomienie po zdarzeniu**.
Kreator ma cztery kroki:

1. **Adres** - nazwa, URL i klucz do podpisu. Klucz generuje się sam
   (`whsec_…`). Skopiuj go od razu: po zapisaniu pokazujemy go już tylko
   zamaskowany. **Nowy klucz** generuje inny; wtedy podmień go też u siebie.
2. **Zdarzenia** - które zdarzenia mają przychodzić. `call.completed` jest
   zawsze włączone.
3. **Agenci** - wszyscy albo tylko wybrani. Zdarzenia przypisane do agenta
   (rozmowy, SMS-y, kampanie) innych agentów nie trafią na ten adres.
   `balance.low` przychodzi zawsze.
4. **Test** - wysyła przykładowe zdarzenie, podpisane tak jak prawdziwe.
   Widzisz kod odpowiedzi Twojego serwera, wysłaną treść i nagłówek podpisu.

Webhook możesz potem edytować w tym samym kreatorze (lista **Twoje
integracje** → **Powiadomienia**). Ten sam webhook jest też widoczny
w katalogu jako **Webhook po rozmowie**.

<Note>
  Webhooki są dostępne od planu **Start**.
</Note>

## Format wiadomości

Każde zdarzenie ma tę samą kopertę:

```json theme={null}
{
  "event": "call.completed",
  "sent_at": "2026-10-13T10:15:00+00:00",
  "data": { "...": "..." }
}
```

Nagłówki:

| Nagłówek | Wartość |
| - | - |
| `Content-Type` | `application/json` |
| `X-Brosbots-Signature` | `sha256=<hex>`: HMAC-SHA256 surowej treści żądania, kluczem jest Twój klucz do podpisu |

Przykład z kreatora ma w `data` pole `"test": true`, więc łatwo go odfiltrować.

## Zdarzenia

| Zdarzenie | Kiedy | Najważniejsze pola `data` |
| - | - | - |
| `call.completed` | po każdej rozmowie (zawsze włączone) | `workflow_run_id`, `workflow_id`, `workflow_name`, `duration_seconds`, `initial_context`, `gathered_context`, `recording_download_url`, `transcript_download_url` |
| `call.started` | rozmowa się zaczyna | `workflow_run_id`, `workflow_id`, `direction`, `phone_number`, `channel`, `campaign_id`, `started_at` |
| `sms.received` | przychodzący SMS | `sms_id`, `from_number`, `to_number`, `text`, `workflow_id`, `workflow_run_id`, `received_at` |
| `campaign.completed` | kampania obdzwoniła wszystkie kontakty | podsumowanie kampanii i `rows` (wynik każdego kontaktu) |
| `balance.low` | saldo spada do progu ostrzeżenia albo poniżej zera | `balance`, `in_debt`, `minutes_left` |
| `lead.dropped` | lead, do którego agent nie zadzwonił: brak środków przez godzinę, zajęte kanały, przepełniona kolejka, limit zgłoszeń, błąd wybierania ([Lista Leady](/pl/integrations/leads/lead-webhook#lista-leady)) | `lead_id`, `workflow_id`, `workflow_name`, `source`, `phone_number`, `name`, `status`, `reason`, `attempts`, `created_at` |

Pełne przykłady i opis pól: [Payload webhooka po rozmowie](/pl/voice-agent/conversation-flow#payload-webhooka-po-rozmowie).

## Sprawdzanie podpisu

Policz HMAC-SHA256 z **surowej** treści żądania (bajty tak, jak przyszły, przed
parsowaniem JSON-a) i porównaj z nagłówkiem w czasie stałym. Ponowne
zserializowanie JSON-a może zmienić kolejność kluczy albo spacje, a wtedy
podpis się nie zgodzi.

<CodeGroup>
  ```javascript Node.js (Express) theme={null}
  import crypto from "node:crypto";
  import express from "express";

  const app = express();
  const SECRET = process.env.BROSBOTS_WEBHOOK_SECRET;

  app.post("/webhooks/brosbots", express.raw({ type: "application/json" }), (req, res) => {
    const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
    const received = req.get("X-Brosbots-Signature") || "";
    const ok =
      received.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
    if (!ok) return res.status(401).end();

    const { event, data } = JSON.parse(req.body.toString("utf8"));
    // ... obsłuż zdarzenie
    res.status(204).end();
  });
  ```

  ```python Python (FastAPI) theme={null}
  import hashlib
  import hmac
  import os

  from fastapi import FastAPI, Header, HTTPException, Request

  app = FastAPI()
  SECRET = os.environ["BROSBOTS_WEBHOOK_SECRET"].encode()


  @app.post("/webhooks/brosbots", status_code=204)
  async def brosbots(request: Request, x_brosbots_signature: str = Header("")):
      body = await request.body()
      expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
      if not hmac.compare_digest(expected, x_brosbots_signature):
          raise HTTPException(status_code=401)
      payload = await request.json()
      # ... obsłuż payload["event"], payload["data"]
  ```

  ```php PHP theme={null}
  <?php
  $secret = getenv('BROSBOTS_WEBHOOK_SECRET');
  $body = file_get_contents('php://input');
  $expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
  $received = $_SERVER['HTTP_X_BROSBOTS_SIGNATURE'] ?? '';

  if (!hash_equals($expected, $received)) {
      http_response_code(401);
      exit;
  }
  $payload = json_decode($body, true);
  // ... obsłuż $payload['event'], $payload['data']
  http_response_code(204);
  ```
</CodeGroup>

W Make, Zapierze i n8n podpisu zwykle się nie sprawdza: adres webhooka jest
tam tajny sam w sobie. Klucz i tak się przydaje, jeśli adres kiedyś wycieknie.

## Dostarczanie i ponowienia

* Odpowiedz kodem `2xx` w ciągu 15 sekund. Treść odpowiedzi nie ma znaczenia.
* Przy błędzie połączenia, przekroczeniu czasu albo kodach `408`, `425`,
  `429`, `5xx` ponawiamy jeszcze dwa razy (po 1 i po 5 sekundach). Inne kody
  `4xx` uznajemy za odrzucenie i nie ponawiamy.
* Każda próba trafia do **Ostatnich wysyłek** pod katalogiem integracji,
  z kodem odpowiedzi i błędem.
* To samo zdarzenie może w rzadkich przypadkach przyjść dwa razy. Do
  rozpoznania powtórki użyj `event` i `data.workflow_run_id` (albo
  `data.sms_id`, `data.campaign_id`).

## Bezpieczeństwo

* Adres musi być publiczny: adresy w sieci prywatnej i `localhost` są
  blokowane.
* Klucz do podpisu jest przechowywany zaszyfrowany i nie wraca z API w całości.
* Linki do nagrań i transkrypcji w `call.completed` działają bez logowania.
  Traktuj je jak dane osobowe.


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