Webhooks
スクレイプ、クロール、サイトマップ、検索、スケジュールタスクなど、AnyCrawl のイベントをリアルタイムで受け取ります。
はじめに
Webhook では、AnyCrawl アカウントでイベントが発生したときに、HTTP でリアルタイム通知を受け取れます。ポーリングは不要で、イベントごとに指定したエンドポイントへ AnyCrawl が POST します。
主な特徴: 複数イベント種別の購読、HMAC-SHA256 署名検証、指数バックオフ付き自動再試行、配信履歴、プライベート IP 保護。
主な機能
- イベント購読: スクレイプ、クロール、サイトマップ、検索、スケジュールタスク、システムイベント
- 安全な配信: HMAC-SHA256 で真正性を検証
- 自動再試行: 失敗時は指数バックオフで再試行
- 配信の追跡: すべての Webhook 配信履歴
- スコープ: すべてのイベント、または特定タスクのみ
- カスタムヘッダー: Webhook リクエストに任意の HTTP ヘッダーを追加
- プライベート IP 保護: SSRF 対策を内蔵
API エンドポイント
POST /v1/webhooks # 購読を作成
GET /v1/webhooks # 一覧
GET /v1/webhooks/:webhookId # 詳細
PUT /v1/webhooks/:webhookId # 更新
DELETE /v1/webhooks/:webhookId # 削除
GET /v1/webhooks/:webhookId/deliveries # 配信履歴
POST /v1/webhooks/:webhookId/test # テスト送信
PUT /v1/webhooks/:webhookId/activate # 有効化
PUT /v1/webhooks/:webhookId/deactivate # 無効化
POST /v1/webhooks/:webhookId/deliveries/:deliveryId/replay # 失敗分の再送
GET /v1/webhook-events # 対応イベント一覧対応イベント
ジョブ
| イベント | 説明 | 発火タイミング |
|---|---|---|
scrape.created | スクレイプジョブ作成 | キューに入ったとき |
scrape.started | スクレイプ開始 | 実行が始まったとき |
scrape.completed | スクレイプ完了 | 成功終了 |
scrape.failed | スクレイプ失敗 | エラー発生 |
scrape.cancelled | スクレイプ取消 | 手動キャンセル |
crawl.created | クロールジョブ作成 | キューに入ったとき |
crawl.started | クロール開始 | 実行が始まったとき |
crawl.completed | クロール完了 | 成功終了 |
crawl.failed | クロール失敗 | エラー発生 |
crawl.cancelled | クロール取消 | 手動キャンセル |
スケジュールタスク
| イベント | 説明 | 発火タイミング |
|---|---|---|
task.executed | タスク実行 | スケジュール実行時 |
task.failed | タスク失敗 | 失敗時 |
task.paused | 一時停止 | 停止されたとき |
task.resumed | 再開 | 再開されたとき |
検索
| イベント | 説明 | 発火タイミング |
|---|---|---|
search.created | 検索ジョブ作成 | キューに入ったとき |
search.started | 検索開始 | 実行が始まったとき |
search.completed | 検索完了 | 成功終了 |
search.failed | 検索失敗 | エラー発生 |
サイトマップ(map)
| イベント | 説明 | 発火タイミング |
|---|---|---|
map.created | map ジョブ作成 | キューに入ったとき |
map.started | map 開始 | 実行が始まったとき |
map.completed | map 完了 | 成功終了 |
map.failed | map 失敗 | エラー発生 |
テスト
| イベント | 説明 | 発火タイミング |
|---|---|---|
webhook.test | テスト | 手動テスト送信時 |
クイックスタート
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
}'レスポンス
{
"success": true,
"data": {
"webhook_id": "webhook-uuid-here",
"secret": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6",
"message": "Webhook created successfully. Save the secret - it won't be shown again."
}
}重要: secret は作成直後に必ず保存してください。表示は一度きりで、署名検証に必要です。
リクエストパラメータ
Webhook 設定
| パラメータ | 型 | 必須 | 既定値 | 説明 |
|---|---|---|---|---|
name | string | はい | - | 名前(1〜255 文字) |
description | string | いいえ | - | 説明 |
webhook_url | string | はい | - | 受信 URL(本番は HTTPS 推奨) |
event_types | string[] | はい | - | 購読するイベント種別 |
scope | string | いいえ | "all" | "all" または "specific" |
specific_task_ids | string[] | いいえ | - | scope が "specific" のとき必須のタスク ID |
配信設定
| パラメータ | 型 | 必須 | 既定値 | 説明 |
|---|---|---|---|---|
timeout_seconds | number | いいえ | 10 | タイムアウト(1〜60 秒) |
max_retries | number | いいえ | 3 | 最大再試行回数(0〜10) |
retry_backoff_multiplier | number | いいえ | 2 | バックオフ倍率(1〜10) |
custom_headers | object | いいえ | - | 任意の HTTP ヘッダー |
連続 10 回失敗すると Webhook は自動で無効化され、過剰な再試行を防ぎます。原因を直したら手動で再有効化できます。
メタデータ
| パラメータ | 型 | 必須 | 既定値 | 説明 |
|---|---|---|---|---|
tags | string[] | いいえ | - | 整理用タグ |
metadata | object | いいえ | - | 任意のメタデータ |
Webhook ペイロード形式
HTTP ヘッダー
各リクエストに含まれるヘッダー例:
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.000Zペイロード例
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"
}署名の検証
なぜ検証するか
署名が正しければ、リクエストが AnyCrawl 由来で改ざんされていないことを確認できます。
アルゴリズム
ペイロードは HMAC-SHA256 で署名されます。
signature = HMAC-SHA256(payload, webhook_secret)
header_value = "sha256=" + hex(signature)実装例
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;
// 署名を検証
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// イベント情報
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);
// すぐに応答(5 秒以内推奨)
res.status(200).json({ received: true });
// 本体処理は非同期
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()
# 署名を検証
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
# イベント情報
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}')
# すぐに応答
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)
}Webhook の管理
一覧
curl -X GET "https://api.anycrawl.dev/v1/webhooks" \
-H "Authorization: Bearer <your-api-key>"レスポンス
{
"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 は一覧・詳細では常にマスクされます。
更新
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"]
}'シークレットは更新できません。変更する場合は Webhook を削除して作り直してください。
テスト
設定を確認するためテストイベントを送ります。
curl -X POST "https://api.anycrawl.dev/v1/webhooks/:webhookId/test" \
-H "Authorization: Bearer <your-api-key>"テストペイロード:
{
"message": "This is a test webhook from AnyCrawl",
"timestamp": "2026-01-27T10:00:00.000Z",
"webhook_id": "webhook-uuid-1"
}無効化 / 有効化
curl -X PUT "https://api.anycrawl.dev/v1/webhooks/:webhookId/deactivate" \
-H "Authorization: Bearer <your-api-key>"削除
curl -X DELETE "https://api.anycrawl.dev/v1/webhooks/:webhookId" \
-H "Authorization: Bearer <your-api-key>"Webhook を削除すると、配信履歴もすべて削除されます。
失敗した配信の再送
失敗した配信を手動で再試行します。
curl -X POST "https://api.anycrawl.dev/v1/webhooks/:webhookId/deliveries/:deliveryId/replay" \
-H "Authorization: Bearer <your-api-key>"レスポンス:
{
"success": true,
"message": "Delivery replayed successfully",
"data": {
"delivery_id": "delivery-uuid-1",
"status": "pending"
}
}再送は同じペイロードで新しい配信試行を作ります。エンドポイント修正後に失敗分をやり直すのに便利です。
配信履歴
一覧取得
curl -X GET "https://api.anycrawl.dev/v1/webhooks/:webhookId/deliveries?limit=20" \
-H "Authorization: Bearer <your-api-key>"クエリパラメータ
| パラメータ | 型 | 既定値 | 説明 |
|---|---|---|---|
limit | number | 100 | 取得件数 |
offset | number | 0 | スキップ件数 |
status | string | - | delivered、failed、retrying でフィルター |
from | string | - | 開始日(ISO 8601) |
to | string | - | 終了日(ISO 8601) |
レスポンス
{
"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
}
}
}再試行
いつ再試行されるか
次の場合に再試行されます。
- HTTP ステータスが 2xx 以外
- 接続タイムアウト
- ネットワークエラー
スケジュール
既定(max_retries: 3、retry_backoff_multiplier: 2)の例:
| 試行 | 遅延 | 初回からの経過 |
|---|---|---|
| 1 回目の再試行 | 1 分 | 1 分 |
| 2 回目の再試行 | 2 分 | 3 分 |
| 3 回目の再試行 | 4 分 | 7 分 |
遅延は backoff_multiplier ^ (attempt - 1) × 1 分 で計算されます。
自動無効化
連続 10 回失敗すると Webhook は自動で無効化され、過剰な再試行を防ぎます。
再有効化:
curl -X PUT "https://api.anycrawl.dev/v1/webhooks/:webhookId/activate" \
-H "Authorization: Bearer <your-api-key>"スコープ
すべてのイベント(scope: "all")
購読した種別のイベントをすべて受け取ります。
{
"scope": "all",
"event_types": ["scrape.completed", "crawl.completed"]
}特定タスク(scope: "specific")
指定したスケジュールタスクの通知だけを受け取ります。
{
"scope": "specific",
"specific_task_ids": ["task-uuid-1", "task-uuid-2"],
"event_types": ["task.executed", "task.failed"]
}プライベート IP 保護
既定の動作
AnyCrawl はプライベート IP への Webhook 配信をブロックします。
10.0.0.0/8172.16.0.0/12192.168.0.0/16169.254.0.0/16(リンクローカル)127.0.0.1/localhost- IPv6 のプライベートアドレス
ローカル Webhook を許可(テストのみ)
ローカル開発時:
ALLOW_LOCAL_WEBHOOKS=true本番では有効にしないでください。重大なセキュリティリスクがあります。
ベストプラクティス
1. すぐに応答する
5 秒以内に 2xx を返してください。
app.post('/webhook', async (req, res) => {
// 署名を検証
if (!verifySignature(req.body, req.headers['x-anycrawl-signature'])) {
return res.status(401).send('Invalid signature');
}
// すぐに受領応答
res.status(200).json({ received: true });
// 本体は非同期
queue.add('process-webhook', req.body);
});2. 冪等性
X-Webhook-Delivery-Id で重複処理を防ぎます。
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);
// イベント処理…
res.status(200).json({ received: true });
});3. 適切なステータスコード
| ステータス | 意味 | AnyCrawl の挙動 |
|---|---|---|
| 200-299 | 成功 | 再試行しない |
| 400-499 | クライアントエラー | 再試行しない(失敗として記録) |
| 500-599 | サーバーエラー | バックオフで再試行 |
| タイムアウト | ネットワーク | バックオフで再試行 |
4. ログを残す
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. セキュリティチェックリスト
- ✅ 署名を必ず検証する
- ✅ 本番は HTTPS を使う
- ✅ シークレットを URL に含めない
- ✅ レート制限を実装する
- ✅ 異常を監視する
- ✅ ペイロード構造を検証する
よくある使い方
Slack 通知
スクレイプ結果を 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 });
});メール通知
失敗時にメールを送る例:
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 });
});DB に記録
Webhook を DB に保存する例:
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 });
});トラブルシューティング
イベントが届かない
確認:
is_active: trueかevent_typesは正しいか- URL はインターネットから到達できるか
- プライベート IP 保護でブロックされていないか
scope(all / specific)は意図どおりか
署名検証が失敗する
よくある原因:
- シークレットが違う(作成時のレスポンスを確認)
- ハッシュ前にペイロードを文字列化していない
- JSON の空白・改行が一致していない
- HMAC が SHA-256 以外
失敗が多い
対処:
- 5 秒以内に応答できるか
- 適切な HTTP ステータスを返しているか
- 配信履歴のエラーメッセージを確認
- ngrok などでローカル検証
自動無効化された
原因: 連続 10 回失敗
対処:
- エンドポイント・署名・ネットワークなど根本原因を修正
- テスト API で動作確認
- Webhook を再有効化:
curl -X PUT "https://api.anycrawl.dev/v1/webhooks/:webhookId/activate" \
-H "Authorization: Bearer <your-api-key>"デバッグ用ツール
外部サービス
- webhook.site — リクエストを確認
- requestbin.com — リクエスト検査
- ngrok — ローカルをトンネル
ローカル開発
ngrok でローカルサーバーを公開:
ngrok http 3000表示された ngrok URL を Webhook URL に設定:
https://abc123.ngrok.io/webhooks/anycrawl制限
| 項目 | 制限 |
|---|---|
| 名前の長さ | 1〜255 文字 |
| Webhook URL | 本番は HTTPS 推奨 |
| タイムアウト | 1〜60 秒 |
| 最大再試行 | 0〜10 |
| ペイロードサイズ | 最大 1MB |
| カスタムヘッダー | 最大 20 個 |
| イベント種別数 | 上限なし |
関連ドキュメント
- スケジュールタスク — 定期ジョブの自動化
- Scrape API — スクレイプ
- Crawl API — クロール