Webhooks
Nhận thông báo thời gian thực cho mọi sự kiện AnyCrawl: scrape, crawl, map, search và tác vụ theo lịch.
Giới thiệu
Webhooks cho phép bạn nhận thông báo HTTP thời gian thực khi có sự kiện trong tài khoản AnyCrawl. Thay vì polling, AnyCrawl tự gửi request POST tới endpoint bạn chỉ định khi có sự kiện.
Điểm chính: Đăng ký nhiều loại sự kiện, xác minh chữ ký HMAC-SHA256, thử lại tự động với backoff lũy thừa, lịch sử gửi và bảo vệ IP riêng.
Tính năng cốt lõi
- Đăng ký sự kiện: scrape, crawl, map, search, tác vụ theo lịch và sự kiện hệ thống
- Gửi an toàn: Xác minh chữ ký HMAC-SHA256 để đảm bảo tính xác thực
- Thử lại tự động: Cơ chế backoff lũy thừa khi gửi thất bại
- Theo dõi gửi: Toàn bộ lịch sử gửi webhook
- Lọc phạm vi: Tất cả sự kiện hoặc chỉ các tác vụ cụ thể
- Header tùy chỉnh: Thêm HTTP header tùy chỉnh cho request webhook
- Bảo vệ IP riêng: Chống SSRF tích hợp
Endpoint API
POST /v1/webhooks # Tạo đăng ký webhook
GET /v1/webhooks # Liệt kê webhook
GET /v1/webhooks/:webhookId # Chi tiết webhook
PUT /v1/webhooks/:webhookId # Cập nhật webhook
DELETE /v1/webhooks/:webhookId # Xóa webhook
GET /v1/webhooks/:webhookId/deliveries # Lịch sử gửi
POST /v1/webhooks/:webhookId/test # Gửi webhook thử
PUT /v1/webhooks/:webhookId/activate # Kích hoạt webhook
PUT /v1/webhooks/:webhookId/deactivate # Vô hiệu hóa webhook
POST /v1/webhooks/:webhookId/deliveries/:deliveryId/replay # Gửi lại lần thất bại
GET /v1/webhook-events # Danh sách sự kiện hỗ trợSự kiện hỗ trợ
Sự kiện job
| Sự kiện | Mô tả | Kích hoạt khi |
|---|---|---|
scrape.created | Job scrape đã tạo | Job scrape mới xếp hàng |
scrape.started | Job scrape đã bắt đầu | Job bắt đầu thực thi |
scrape.completed | Job scrape hoàn thành | Job thành công |
scrape.failed | Job scrape thất bại | Job gặp lỗi |
scrape.cancelled | Job scrape đã hủy | Job bị hủy thủ công |
crawl.created | Job crawl đã tạo | Job crawl mới xếp hàng |
crawl.started | Job crawl đã bắt đầu | Job bắt đầu thực thi |
crawl.completed | Job crawl hoàn thành | Job thành công |
crawl.failed | Job crawl thất bại | Job gặp lỗi |
crawl.cancelled | Job crawl đã hủy | Job bị hủy thủ công |
Sự kiện tác vụ theo lịch
| Sự kiện | Mô tả | Kích hoạt khi |
|---|---|---|
task.executed | Tác vụ đã chạy | Tác vụ theo lịch thực thi |
task.failed | Tác vụ thất bại | Tác vụ theo lịch lỗi |
task.paused | Tác vụ tạm dừng | Tác vụ bị tạm dừng |
task.resumed | Tác vụ tiếp tục | Tác vụ được tiếp tục |
Sự kiện search
| Sự kiện | Mô tả | Kích hoạt khi |
|---|---|---|
search.created | Job search đã tạo | Job search mới xếp hàng |
search.started | Job search đã bắt đầu | Job bắt đầu thực thi |
search.completed | Job search hoàn thành | Job thành công |
search.failed | Job search thất bại | Job gặp lỗi |
Sự kiện map
| Sự kiện | Mô tả | Kích hoạt khi |
|---|---|---|
map.created | Job map đã tạo | Job map mới xếp hàng |
map.started | Job map đã bắt đầu | Job bắt đầu thực thi |
map.completed | Job map hoàn thành | Job thành công |
map.failed | Job map thất bại | Job gặp lỗi |
Sự kiện thử
| Sự kiện | Mô tả | Kích hoạt khi |
|---|---|---|
webhook.test | Sự kiện thử | Gửi webhook thử thủ công |
Bắt đầu nhanh
Tạo webhook
curl -X POST "https://api.anycrawl.dev/v1/webhooks" \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Notifications",
"webhook_url": "https://your-domain.com/webhooks/anycrawl",
"event_types": ["scrape.completed", "scrape.failed", "crawl.completed"],
"scope": "all",
"timeout_seconds": 10,
"max_retries": 3
}'Phản hồi
{
"success": true,
"data": {
"webhook_id": "webhook-uuid-here",
"secret": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6",
"message": "Webhook created successfully. Save the secret - it won't be shown again."
}
}Quan trọng: Lưu secret ngay! Chỉ hiển thị một lần khi tạo và cần để xác minh chữ ký.
Tham số request
Cấu hình webhook
| Tham số | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
name | string | Có | - | Tên webhook (1-255 ký tự) |
description | string | Không | - | Mô tả webhook |
webhook_url | string | Có | - | URL endpoint (khuyến nghị HTTPS) |
event_types | string[] | Có | - | Mảng loại sự kiện đăng ký |
scope | string | Không | "all" | Phạm vi: "all" hoặc "specific" |
specific_task_ids | string[] | Không | - | ID tác vụ (bắt buộc nếu scope là "specific") |
Cấu hình gửi
| Tham số | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
timeout_seconds | number | Không | 10 | Timeout request (1-60 giây) |
max_retries | number | Không | 3 | Số lần thử tối đa (0-10) |
retry_backoff_multiplier | number | Không | 2 | Hệ số backoff (1-10) |
custom_headers | object | Không | - | HTTP header tùy chỉnh |
Webhook tự vô hiệu sau 10 lỗi liên tiếp để tránh thử lại quá mức. Bạn có thể kích hoạt lại thủ công sau khi sửa lỗi.
Metadata
| Tham số | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
tags | string[] | Không | - | Thẻ tổ chức |
metadata | object | Không | - | Metadata tùy chỉnh |
Định dạng payload webhook
HTTP Headers
Mọi request webhook kèm các header sau:
Content-Type: application/json
X-AnyCrawl-Signature: sha256=abc123...
X-Webhook-Event: scrape.completed
X-Webhook-Delivery-Id: delivery-uuid-1
X-Webhook-Timestamp: 2026-01-27T10:00:00.000ZVí dụ payload
scrape.completed
{
"job_id": "job-uuid-1",
"status": "completed",
"url": "https://example.com",
"total": 10,
"completed": 10,
"failed": 0,
"credits_used": 5,
"created_at": "2026-01-27T09:00:00.000Z",
"completed_at": "2026-01-27T10:00:00.000Z"
}scrape.failed
{
"job_id": "job-uuid-1",
"status": "failed",
"url": "https://example.com",
"error_message": "Connection timeout",
"credits_used": 3,
"created_at": "2026-01-27T09:00:00.000Z",
"completed_at": "2026-01-27T10:00:00.000Z"
}task.executed
{
"task_id": "task-uuid-1",
"task_name": "Daily News Scrape",
"execution_id": "exec-uuid-1",
"execution_number": 45,
"status": "completed",
"job_id": "job-uuid-1",
"credits_used": 5,
"scheduled_for": "2026-01-27T09:00:00.000Z",
"completed_at": "2026-01-27T09:02:15.000Z"
}Xác minh chữ ký
Vì sao cần xác minh?
Xác minh chữ ký đảm bảo request webhook thật sự từ AnyCrawl và không bị sửa đổi, chống request độc hại.
Thuật toán
AnyCrawl dùng HMAC-SHA256 để ký payload:
signature = HMAC-SHA256(payload, webhook_secret)
header_value = "sha256=" + hex(signature)Ví dụ triển khai
Node.js / Express
const crypto = require('crypto');
const express = require('express');
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(payload));
const expectedSignature = `sha256=${hmac.digest('hex')}`;
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
const app = express();
app.use(express.json());
app.post('/webhooks/anycrawl', (req, res) => {
const signature = req.headers['x-anycrawl-signature'];
const secret = process.env.WEBHOOK_SECRET;
// Verify signature
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Extract event info
const eventType = req.headers['x-webhook-event'];
const deliveryId = req.headers['x-webhook-delivery-id'];
console.log(`Received event: ${eventType}`);
console.log(`Delivery ID: ${deliveryId}`);
console.log('Payload:', req.body);
// Respond quickly (< 5 seconds recommended)
res.status(200).json({ received: true });
// Process asynchronously
processWebhookAsync(eventType, req.body).catch(console.error);
});
app.listen(3000);Python / Flask
import hmac
import hashlib
import json
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = 'your-webhook-secret-here'
def verify_webhook_signature(payload, signature, secret):
expected_signature = 'sha256=' + hmac.new(
secret.encode('utf-8'),
json.dumps(payload).encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
@app.route('/webhooks/anycrawl', methods=['POST'])
def webhook_handler():
signature = request.headers.get('X-AnyCrawl-Signature')
payload = request.get_json()
# Verify signature
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
# Extract event info
event_type = request.headers.get('X-Webhook-Event')
delivery_id = request.headers.get('X-Webhook-Delivery-Id')
print(f'Received event: {event_type}')
print(f'Delivery ID: {delivery_id}')
print(f'Payload: {payload}')
# Respond quickly
return jsonify({'received': True}), 200
if __name__ == '__main__':
app.run(port=3000)Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func verifyWebhookSignature(payload []byte, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expectedSignature := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("X-AnyCrawl-Signature")
eventType := r.Header.Get("X-Webhook-Event")
secret := os.Getenv("WEBHOOK_SECRET")
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading body", http.StatusBadRequest)
return
}
if !verifyWebhookSignature(body, signature, secret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
var payload map[string]interface{}
if err := json.Unmarshal(body, &payload); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
fmt.Printf("Received event: %s\n", eventType)
fmt.Printf("Payload: %+v\n", payload)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
func main() {
http.HandleFunc("/webhooks/anycrawl", webhookHandler)
http.ListenAndServe(":3000", nil)
}Quản lý webhook
Liệt kê tất cả webhook
curl -X GET "https://api.anycrawl.dev/v1/webhooks" \
-H "Authorization: Bearer <your-api-key>"Phản hồi
{
"success": true,
"data": [
{
"uuid": "webhook-uuid-1",
"name": "Production Notifications",
"webhook_url": "https://your-domain.com/webhooks/anycrawl",
"webhook_secret": "***hidden***",
"event_types": ["scrape.completed", "scrape.failed"],
"scope": "all",
"is_active": true,
"consecutive_failures": 0,
"total_deliveries": 145,
"successful_deliveries": 142,
"failed_deliveries": 3,
"last_success_at": "2026-01-27T10:00:00.000Z",
"last_failure_at": "2026-01-26T15:30:00.000Z",
"created_at": "2026-01-01T00:00:00.000Z"
}
]
}webhook_secret luôn bị ẩn trong danh sách và chi tiết vì lý do bảo mật.
Cập nhật webhook
curl -X PUT "https://api.anycrawl.dev/v1/webhooks/:webhookId" \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"event_types": ["scrape.completed", "scrape.failed", "crawl.completed"]
}'Không thể cập nhật secret webhook. Để đổi, hãy xóa và tạo lại webhook.
Thử webhook
Gửi sự kiện thử để kiểm tra cấu hình:
curl -X POST "https://api.anycrawl.dev/v1/webhooks/:webhookId/test" \
-H "Authorization: Bearer <your-api-key>"Payload thử:
{
"message": "This is a test webhook from AnyCrawl",
"timestamp": "2026-01-27T10:00:00.000Z",
"webhook_id": "webhook-uuid-1"
}Vô hiệu / kích hoạt webhook
curl -X PUT "https://api.anycrawl.dev/v1/webhooks/:webhookId/deactivate" \
-H "Authorization: Bearer <your-api-key>"Xóa webhook
curl -X DELETE "https://api.anycrawl.dev/v1/webhooks/:webhookId" \
-H "Authorization: Bearer <your-api-key>"Xóa webhook cũng xóa toàn bộ lịch sử gửi.
Gửi lại lần thất bại
Thử lại thủ công một lần gửi thất bại:
curl -X POST "https://api.anycrawl.dev/v1/webhooks/:webhookId/deliveries/:deliveryId/replay" \
-H "Authorization: Bearer <your-api-key>"Phản hồi:
{
"success": true,
"message": "Delivery replayed successfully",
"data": {
"delivery_id": "delivery-uuid-1",
"status": "pending"
}
}Gửi lại tạo một lần gửi mới với cùng payload. Hữu ích sau khi sửa endpoint.
Lịch sử gửi
Xem các lần gửi
curl -X GET "https://api.anycrawl.dev/v1/webhooks/:webhookId/deliveries?limit=20" \
-H "Authorization: Bearer <your-api-key>"Tham số truy vấn
| Tham số | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
limit | number | 100 | Số lần gửi trả về |
offset | number | 0 | Số bản ghi bỏ qua |
status | string | - | Lọc theo trạng thái: delivered, failed, retrying |
from | string | - | Ngày bắt đầu (ISO 8601) |
to | string | - | Ngày kết thúc (ISO 8601) |
Phản hồi
{
"success": true,
"data": [
{
"uuid": "delivery-uuid-1",
"webhookSubscriptionUuid": "webhook-uuid-1",
"eventType": "scrape.completed",
"status": "delivered",
"attempt_number": 1,
"request_url": "https://your-domain.com/webhooks/anycrawl",
"request_method": "POST",
"response_status": 200,
"response_duration_ms": 125,
"created_at": "2026-01-27T10:00:00.000Z",
"delivered_at": "2026-01-27T10:00:00.125Z"
},
{
"uuid": "delivery-uuid-2",
"status": "failed",
"attempt_number": 3,
"error_message": "Connection timeout",
"error_code": "ETIMEDOUT",
"created_at": "2026-01-27T09:00:00.000Z"
}
],
"meta": {
"limit": 20,
"offset": 0,
"filters": {
"status": null,
"from": null,
"to": null
}
}
}Cơ chế thử lại
Khi nào thử lại
Webhook được thử lại khi:
- Mã HTTP không phải 2xx
- Hết thời gian kết nối
- Lỗi mạng
Lịch thử lại
Với mặc định (max_retries: 3, retry_backoff_multiplier: 2):
| Lần thử | Độ trễ | Thời gian sau lần đầu |
|---|---|---|
| Lần 1 | 1 phút | 1 phút |
| Lần 2 | 2 phút | 3 phút |
| Lần 3 | 4 phút | 7 phút |
Công thức độ trễ: backoff_multiplier ^ (attempt - 1) × 1 phút
Tự vô hiệu hóa
Webhook tự vô hiệu sau 10 lỗi liên tiếp để tránh thử lại quá mức.
Để bật lại:
curl -X PUT "https://api.anycrawl.dev/v1/webhooks/:webhookId/activate" \
-H "Authorization: Bearer <your-api-key>"Lọc phạm vi
Mọi sự kiện (scope: "all")
Nhận thông báo cho mọi sự kiện thuộc các loại đã đăng ký:
{
"scope": "all",
"event_types": ["scrape.completed", "crawl.completed"]
}Tác vụ cụ thể (scope: "specific")
Chỉ nhận thông báo cho các tác vụ theo lịch được chỉ định:
{
"scope": "specific",
"specific_task_ids": ["task-uuid-1", "task-uuid-2"],
"event_types": ["task.executed", "task.failed"]
}Bảo vệ IP riêng
Hành vi mặc định
AnyCrawl chặn gửi webhook tới địa chỉ IP riêng:
10.0.0.0/8172.16.0.0/12192.168.0.0/16169.254.0.0/16(link-local)127.0.0.1/localhost- Địa chỉ IPv6 riêng
Cho phép webhook cục bộ (chỉ thử nghiệm)
Khi phát triển cục bộ, đặt:
ALLOW_LOCAL_WEBHOOKS=trueKhông bật tùy chọn này trên môi trường production — rủi ro bảo mật nghiêm trọng.
Thực hành tốt
1. Phản hồi nhanh
Trả mã trạng thái 2xx trong 5 giây:
app.post('/webhook', async (req, res) => {
// Verify signature
if (!verifySignature(req.body, req.headers['x-anycrawl-signature'])) {
return res.status(401).send('Invalid signature');
}
// Quick acknowledgment
res.status(200).json({ received: true });
// Process asynchronously
queue.add('process-webhook', req.body);
});2. Idempotent
Dùng X-Webhook-Delivery-Id để tránh xử lý trùng:
const processedDeliveries = new Set();
app.post('/webhook', (req, res) => {
const deliveryId = req.headers['x-webhook-delivery-id'];
if (processedDeliveries.has(deliveryId)) {
return res.status(200).json({ received: true, duplicate: true });
}
processedDeliveries.add(deliveryId);
// Process event...
res.status(200).json({ received: true });
});3. Mã trạng thái phù hợp
| Mã | Mô tả | Hành vi AnyCrawl |
|---|---|---|
| 200-299 | Thành công | Không thử lại |
| 400-499 | Lỗi phía client | Không thử lại (ghi nhận thất bại) |
| 500-599 | Lỗi máy chủ | Thử lại với backoff |
| Timeout | Hết thời gian mạng | Thử lại với backoff |
4. Ghi log mọi hoạt động webhook
app.post('/webhook', (req, res) => {
const deliveryId = req.headers['x-webhook-delivery-id'];
const eventType = req.headers['x-webhook-event'];
logger.info('Webhook received', {
deliveryId,
eventType,
timestamp: req.headers['x-webhook-timestamp']
});
try {
processWebhook(req.body, eventType);
logger.info('Webhook processed', { deliveryId });
res.status(200).json({ received: true });
} catch (error) {
logger.error('Webhook failed', {
deliveryId,
error: error.message
});
res.status(500).json({ error: 'Processing failed' });
}
});5. Checklist bảo mật
- ✅ Luôn xác minh chữ ký
- ✅ Dùng HTTPS trên production
- ✅ Không để lộ secret trong URL
- ✅ Giới hạn tốc độ (rate limiting)
- ✅ Giám sát bất thường
- ✅ Kiểm tra cấu trúc payload
Tình huống dùng phổ biến
Thông báo Slack
Gửi kết quả scrape lên Slack:
app.post('/webhooks/anycrawl', async (req, res) => {
const { job_id, status, url } = req.body;
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `Job ${status}: ${url}\nJob ID: ${job_id}`
})
});
res.status(200).json({ received: true });
});Cảnh báo email
Gửi email khi thất bại:
app.post('/webhooks/anycrawl', async (req, res) => {
const eventType = req.headers['x-webhook-event'];
if (eventType.endsWith('.failed')) {
await sendEmail({
to: 'admin@example.com',
subject: 'AnyCrawl Job Failed',
body: JSON.stringify(req.body, null, 2)
});
}
res.status(200).json({ received: true });
});Ghi cơ sở dữ liệu
Lưu sự kiện webhook vào database:
app.post('/webhooks/anycrawl', async (req, res) => {
const eventType = req.headers['x-webhook-event'];
const deliveryId = req.headers['x-webhook-delivery-id'];
await db.webhookEvents.create({
deliveryId,
eventType,
payload: req.body,
receivedAt: new Date()
});
res.status(200).json({ received: true });
});Xử lý sự cố
Webhook không nhận sự kiện
Kiểm tra:
- Webhook có đang bật? (
is_active: true) - Loại sự kiện đã cấu hình đúng?
- URL webhook có truy cập được từ internet?
- Có bị chặn bởi bảo vệ IP riêng?
- Phạm vi (
allvsspecific)
Xác minh chữ ký thất bại
Thường gặp:
- Secret sai (đối chiếu phản hồi lúc tạo webhook)
- Không stringify payload trước khi hash
- JSON thừa khoảng trắng hoặc định dạng khác
- Thuật toán HMAC sai (phải SHA-256)
Tỷ lệ thất bại cao
Cách xử lý:
- Endpoint phản hồi trong 5 giây
- Trả đúng mã HTTP
- Xem thông báo lỗi trong lịch sử gửi
- Thử cục bộ với ngrok hoặc công cụ tương tự
Webhook tự tắt
Nguyên nhân: 10 lỗi liên tiếp
Cách xử lý:
- Sửa nguyên nhân (endpoint, xác minh chữ ký, v.v.)
- Thử bằng endpoint test
- Kích hoạt lại webhook:
curl -X PUT "https://api.anycrawl.dev/v1/webhooks/:webhookId/activate" \
-H "Authorization: Bearer <your-api-key>"Công cụ gỡ lỗi
Công cụ thử
- webhook.site — Xem request webhook
- requestbin.com — Kiểm tra request
- ngrok — Tunnel cục bộ để thử
Phát triển cục bộ
Dùng ngrok để expose máy chủ cục bộ:
ngrok http 3000Sau đó dùng URL ngrok làm webhook URL:
https://abc123.ngrok.io/webhooks/anycrawlGiới hạn
| Mục | Giới hạn |
|---|---|
| Độ dài tên webhook | 1-255 ký tự |
| URL webhook | Khuyến nghị HTTPS (production) |
| Timeout | 1-60 giây |
| Số lần thử tối đa | 0-10 |
| Kích thước payload | Tối đa 1MB |
| Header tùy chỉnh | Tối đa 20 |
| Loại sự kiện mỗi webhook | Không giới hạn |
Tài liệu liên quan
- Scheduled Tasks — Tự động hóa job định kỳ
- Scrape API — Endpoint scrape
- Crawl API — Endpoint crawl