# Webhook

> Gửi sự kiện có chữ ký (Standard Webhooks) cho phiên, máy chạy, lượt chạy và phản hồi, có thử lại và gửi lại.

Webhook đẩy sự kiện tới endpoint HTTPS của bạn để không phải hỏi lại liên tục.

## Sự kiện

| Sự kiện | Khi nào |
|---|---|
| `session.ready` | Phiên đã sẵn sàng |
| `session.failed` | Phiên bị lỗi |
| `session.closed` | Phiên đã đóng |
| `worker.offline` / `worker.online` | Máy ngừng hoặc gửi lại nhịp tim |
| `run.completed` | Một lượt chạy tự động hoá kết thúc |
| `feedback.updated` | Một ticket phản hồi của bạn thay đổi |

## Tạo endpoint

```bash
curl -X POST https://browlix.com/api/public/v1/webhooks \
  -H "Authorization: Bearer $BROWLIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"command_id\":\"$(uuidgen)\",\"url\":\"https://example.com/browlix\",\"events\":[\"session.ready\",\"session.closed\"]}"
```

URL phải là `https://` trên tên miền công khai, cổng 443, 2053, 2083, 2087, 2096 hoặc 8443. Phản hồi có khoá ký (`whsec_…`) **một lần duy nhất**; hãy lưu vào kho bí mật.

## Xác minh mọi yêu cầu

Mỗi lần gửi có header `webhook-id`, `webhook-timestamp` và `webhook-signature` ([Standard Webhooks](https://www.standardwebhooks.com) v1). Xác minh phần thân **gốc** trước khi đọc:

```ts tab=TypeScript
import { verifyWebhook, WebhookVerificationError } from "@browlix/sdk";

app.post("/browlix", express.text({ type: "application/json" }), async (req, res) => {
  try {
    const event = await verifyWebhook<{ id: string; type: string; data: unknown }>(
      process.env.BROWLIX_WEBHOOK_SECRET!, req.headers, req.body,
    );
    await handleOnce(event); // webhook-id lặp lại là cùng sự kiện được gửi lại
    res.sendStatus(204);
  } catch (e) {
    res.sendStatus(e instanceof WebhookVerificationError ? 400 : 500);
  }
});
```

```python tab=Python
from standardwebhooks.webhooks import Webhook  # pip install standardwebhooks

wh = Webhook(os.environ["BROWLIX_WEBHOOK_SECRET"])
event = wh.verify(raw_body, headers)  # báo lỗi khi chữ ký sai hoặc thời điểm quá cũ
```

Thời điểm cũ hơn hoặc sớm hơn 5 phút bị từ chối. Xử lý mỗi `webhook-id` một lần: lần thử lại và gửi lại dùng cùng id.

## Thử lại

Lần gửi không nhận được `2xx` trong thời hạn sẽ được thử lại, tổng cộng tối đa 8 lần, sau 1 phút, 5 phút, 30 phút, 2 giờ, 5 giờ, 10 giờ và 10 giờ. `webhooks.deliveries` liệt kê các lần gửi với trạng thái và thời lượng; `webhooks.redeliver` gửi lại một lần đã kết thúc với cùng `webhook-id`; `webhooks.test` gửi một sự kiện `webhook.test` có chữ ký.

## Đổi khoá ký

`webhooks.rotate_secret` trả về khoá mới một lần. Trong `overlap_hours`, khoá cũ vẫn ký song song — mỗi yêu cầu mang cả hai chữ ký — để bên nhận chuyển khoá mà không lỡ sự kiện.
