Tài liệu AnyHook

Dịch vụ trung chuyển webhook (webhook relay). Trỏ bên gửi về AnyHook là bạn có thử lại (retry) tự động, nhật ký sự kiện đầy đủ, xác minh chữ ký thống nhất và phát lại (replay) bằng một cú bấm. Không cần SDK.

Bắt đầu nhanh

AnyHook không cần SDK hay package nào: bạn thay URL webhook trong dashboard của bên gửi, và handler (đoạn code xử lý webhook trên server của bạn) vẫn nhận đúng những request như trước. Có một điều cần tính trước: các lần thử lại và phát lại mang dấu thời gian (timestamp) gốc của bên gửi, nên với những bên gửi có chữ ký hết hạn (Stripe, Slack và các bên khác có khoảng dung sai trên /providers), kiểm tra AnyHook-Signature để chấp nhận chúng.

Cách nhanh nhất (không cần tài khoản): chạy curl -X POST https://anyhook.net/api/v1/quickstart để nhận ngay một URL nhận (inbound URL) đang hoạt động và một API key, phù hợp cho AI agent và các bài test nhanh. Endpoint này hết hạn sau 7 ngày nếu bạn không nhận nó về tài khoản (claim) qua claim_url được trả về. Trỏ bên gửi về inbound_url được trả về, rồi đọc lại sự kiện bằng GET /api/v1/events và GET /api/v1/events/{id}; trường next_steps trong response có sẵn từng lệnh.

  1. 1
    Tạo tài khoản
    Vào anyhook.net/sign-up và tạo một tài khoản miễn phí. Không cần thẻ thanh toán.
  2. 2
    Tạo một App
    Một App đại diện cho một nguồn webhook. Tài khoản mới mở ra ngay trên form tạo App: đặt tên (ví dụ “Stripe Production”) và, nếu muốn, nhập URL server của bạn, nơi AnyHook chuyển tiếp từng sự kiện sau khi nhận. Để trống thì App ghi lại mọi thứ mà không chuyển tiếp. Bạn có thể thêm đích bất cứ lúc nào, tối đa 3 đích mỗi App trên gói Free.
  3. 3
    Trỏ bên gửi về AnyHook
    Trang App hiển thị URL nhận của bạn và nơi mỗi bên gửi phổ biến yêu cầu nhập nó (ví dụ Stripe → Developers → Webhooks). Dán nó vào đó thay cho URL webhook cũ:
    https://in.anyhook.net/{user-slug}/{app-slug}
    Trang này đang theo dõi: request đầu tiên xuất hiện trên trang trong vài giây. Chưa có gì để gửi? Bấm Send a test event trên cùng trang đó.

Định dạng URL nhận

Mỗi App có một URL nhận riêng:

https://in.anyhook.net/{user-slug}/{app-slug}
  • user-slug: định danh tài khoản của bạn, xem được trong phần cài đặt của dashboard.
  • app-slug: riêng cho từng App, tự sinh từ tên App nhưng có thể sửa.

Endpoint nhận chấp nhận mọi request POST, đồng thời trả lời các bước bắt tay xác minh khi thiết lập: một request GET mang hub.mode=subscribe (bước kiểm tra hub.challenge của Meta) được tự động trả lời, kể cả trước khi App được cấu hình. Mọi method khác nhận 405. Trang web và form HTML thông thường cũng có thể post tới endpoint này (xem Code handler). AnyHook trả về HTTP 200 ngay khi sự kiện đã vào hàng đợi bền vững, sau khoảng một giây, dù endpoint đích của bạn nhanh hay chậm, nên bên gửi không bao giờ phải chờ nó.

Kiểu nội dung. AnyHook giữ nguyên từng byte một body dạng text, như application/json, application/x-www-form-urlencoded, XML hoặc văn bản thuần, và chuyển tiếp nó kèm Content-Type gốc. Body được lưu dưới dạng text, nên một payload nhị phân, hoặc một file trong form multipart, sẽ không đến nguyên vẹn.

Nguồn được hỗ trợ

AnyHook tự nhận diện bên gửi từ các header đến và xác minh chữ ký của payload tại edge (lớp máy chủ biên của Cloudflare). Để bật xác minh, chọn nhà cung cấp webhook khi tạo App và dán webhook signing secret (khóa bí mật dùng để ký) mà nhà cung cấp đưa cho bạn, ví dụ whsec_... của Stripe.

Rất nên thêm. Không có signing secret, AnyHook không thể xác minh request đến có thật sự từ nhà cung cấp của bạn hay không. Request chưa xác minh vẫn được chuyển tiếp; thêm secret giúp AnyHook từ chối request giả mạo ngay tại edge, trước khi chúng vào pipeline của bạn.
NguồnHeader chữ kýThuật toán
StripeStripe-SignatureHMAC-SHA256 + dấu thời gian
GitHubX-Hub-Signature-256HMAC-SHA256
Meta (Instagram / Facebook / Threads)X-Hub-Signature-256HMAC-SHA256
ShopifyX-Shopify-Hmac-Sha256HMAC-SHA256 (Base64)
DiscordX-Signature-Ed25519Ed25519
SlackX-Slack-SignatureHMAC-SHA256 + dấu thời gian
LINEx-line-signatureHMAC-SHA256 (Base64)
Xerox-xero-signatureHMAC-SHA256 (Base64)
LinearLinear-SignatureHMAC-SHA256
SentrySentry-Hook-SignatureHMAC-SHA256
Vercelx-vercel-signatureHMAC-SHA1
PaddlePaddle-SignatureHMAC-SHA256 + dấu thời gian
SendGridX-Twilio-Email-Event-Webhook-SignatureECDSA P-256
TwilioX-Twilio-SignatureHMAC-SHA1
IntercomX-Hub-SignatureHMAC-SHA1
HubSpotX-HubSpot-Signature-v3HMAC-SHA256 (v3) / SHA-256 (v1/v2)
Standard Webhooks (Svix, OpenAI, Replicate)webhook-signature / Svix-SignatureHMAC-SHA256 + dấu thời gian
ClerkSvix-SignatureHMAC-SHA256 (qua Svix)
ResendSvix-SignatureHMAC-SHA256 (qua Svix)
Lemon SqueezyX-SignatureHMAC-SHA256
WooCommerceX-WC-Webhook-SignatureHMAC-SHA256 (Base64)
PayPalPAYPAL-TRANSMISSION-SIGRSA-SHA256
Generic / tùy chỉnhKhông cóKhông xác minh

Request không qua được bước kiểm tra bị từ chối tại edge và không bao giờ tới endpoint của bạn. Trang App đếm số request này, và 5 request đầu tiên mỗi ngày được giữ trong nhật ký sự kiện với trạng thái rejected, kèm header và body, để bạn xem vì sao.

Meta: Instagram, Facebook, Threads

Cả ba dùng chung một cơ chế webhook của Graph API, nên một App là đủ cho cả ba. Meta ký bằng X-Hub-Signature-256, cùng header mà GitHub dùng; AnyHook phân biệt hai bên bằng X-Github-Event, header mà GitHub luôn gửi còn Meta thì không bao giờ.

Meta không lưu callback URL cho tới khi URL đó trả lời một request GET mang hub.mode=subscribe bằng cách trả lại hub.challenge làm toàn bộ body của response. AnyHook trả lời thay bạn, nên URL nhận được lưu ngay ở lần thử đầu tiên.

Trong AnyHookTrong Meta App Dashboard
Source: MetaApp settings → Basic
App SecretSao chép từ đó vào AnyHook
Verify Token (không bắt buộc)Webhooks → Verify token: nhập cùng chuỗi đó
URL nhận của bạnWebhooks → Callback URL: dán vào đây
Verify Token đi theo chiều ngược lại. Mọi secret khác ở đây đều được sao chép từ nhà cung cấp. Secret này thì bạn tự đặt, rồi nhập vào Meta. Nó không phải App Secret và không nên trùng chuỗi đó: nó nằm trong query string của bước bắt tay khi thiết lập, mà khóa dùng để ký thì không nên xuất hiện trong URL. Để trống thì việc đăng ký vẫn hoàn tất; nếu đặt, AnyHook sẽ kiểm tra nó trước khi trả lời.

Meta thử lại một lượt gửi thất bại với tần suất giảm dần trong 36 giờ rồi bỏ nó. Vì AnyHook lưu sự kiện trước khi gửi tới endpoint của bạn, một lần deploy hay một sự cố ngừng hoạt động ở phía bạn không làm tiêu tốn khoảng thời gian đó.

Cơ chế gửi

AnyHook dùng kiến trúc bất đồng bộ dựa trên hàng đợi để tách bên gửi webhook khỏi backend của bạn:

  1. Nhận tại edge: Cloudflare Workers nhận request POST, xác minh chữ ký và trả về 200 khi sự kiện đã được lưu bền vững, sau khoảng một giây.
  2. Hàng đợi: sự kiện được đẩy vào một hàng đợi message bền vững (Upstash QStash). Lúc này kết nối HTTP của bên gửi đã đóng.
  3. Chuyển tiếp: QStash gửi sự kiện tới bộ chuyển tiếp (forwarder) của AnyHook, nơi POST payload gốc tới endpoint đích của bạn.
  4. Ghi nhật ký: mọi bước đều được ghi lại, gồm header/body đến, trạng thái gửi, mã và body phản hồi của endpoint, độ trễ.
Vì sao điều này quan trọng. Bên gửi (Stripe, GitHub, v.v.) nhận được mã 200 sau khoảng một giây. Sau đó, backend của bạn có thể mất tối đa bằng timeout của gói để phản hồi (60 giây trên gói Free, 120 giây trên gói Pro, 5 phút trên gói Scale), và bên gửi không thử lại, vì nó đã nhận được mã 200. Thời gian chờ sau giây đầu tiên dùng hạn mức compute (tài nguyên tính toán đi kèm gói).
Giới hạn kích thước payload. Payload tối đa cho mỗi sự kiện: 512 KB (Free), 2 MB (Pro), 4 MB (Scale), tùy chỉnh (Enterprise). Request quá lớn bị từ chối tại edge với 413 Payload Too Large, không bao giờ bị âm thầm bỏ đi, không bao giờ bị tính phí. Trang App đếm số request này.
Mô hình gửi. Theo mặc định, AnyHook chuyển tiếp sự kiện ngay khi chúng đến, không trì hoãn và không giới hạn số lượt gửi đồng thời. Từ gói Pro trở lên, mỗi đích có thể đặt giới hạn tốc độ theo giây và trì hoãn lần gửi đầu tiên. Nếu endpoint của bạn trả về 429, AnyHook lùi lại và thử lại.

Đảm bảo khi gửi

Cam kết, nói thẳng ra, vì một dịch vụ trung chuyển mơ hồ về điều nó đảm bảo sẽ đẩy sự mơ hồ đó sang code của bạn:

  1. Ít nhất một lần (at-least-once), không phải đúng một lần (exactly-once). Thử lại là cốt lõi của sản phẩm, và một lần thử lại sau khi response bị mất không thể phân biệt với một lượt gửi mới. Thỉnh thoảng bạn sẽ nhận cùng một sự kiện hai lần; không dịch vụ trung chuyển nào hứa khác được, và những bên hứa như vậy chỉ đang mô tả kịch bản suôn sẻ của họ. Handler của bạn phải idempotent (xử lý nhiều lần vẫn cho cùng một kết quả), và ID sự kiện chúng tôi chuyển tiếp trong X-AnyHook-Delivery là khóa để chống trùng lặp.
  2. Không đảm bảo thứ tự. Thử lại và gửi đồng thời nghĩa là một sự kiện mới hơn có thể tới trước một sự kiện cũ hơn vẫn đang được thử lại. Phần lớn bên gửi cũng không cam kết thứ tự ngay từ phía họ. Sắp xếp theo một trường bên trong payload (version, updated_at), không bao giờ theo thời điểm đến.
  3. Đừng chống trùng lặp dựa trên chữ ký. AnyHook-Signature được ký lại với dấu thời gian mới ở mỗi lần gửi, có chủ ý, để các lần thử lại không bao giờ rơi ra ngoài khoảng dung sai của bạn. Hai lượt gửi của cùng một sự kiện mang hai chữ ký khác nhau; id của sự kiện mới là thứ không đổi.
  4. Đã nhận nghĩa là đã lưu bền vững. Edge chỉ trả về 200 sau khi sự kiện đã được lưu, và phía sau hàng đợi còn hai kho dự phòng trước khi edge phải trả 503. Mã 503 từ URL nhận nghĩa là không có gì được lưu, và cơ chế thử lại của chính bên gửi là lưới an toàn, đó là lý do chúng tôi trả 503 thay vì giả vờ.
Áp dụng thực tế. Viết handler sao cho xử lý cùng một sự kiện hai lần cũng vô hại, và coi thứ tự đến là nhiễu. Chúng tôi đã viết về các cách làm, xếp theo mức độ bảo vệ bạn, trong Bạn nhận cùng một webhook hai lần. Đó là cam kết, không phải bug.

Lịch thử lại

Khi đích trả về lỗi có thể thử lại hoặc bị timeout, AnyHook tự động thử lại theo exponential backoff (khoảng chờ tăng theo cấp số nhân):

Lần gửiKhoảng chờGói
1Gửi ngayTất cả
25 phút sauTất cả
330 phút sauTất cả
43 giờ sauPro, Scale, Enterprise
518 giờ sauPro, Scale, Enterprise
6-10mỗi lần cách 24 giờScale, Enterprise
Timeout gửi. AnyHook chờ endpoint của bạn phản hồi tối đa 60 giây (Free), 120 giây (Pro) hoặc 5 phút (Scale+). Nếu handler của bạn cần lâu hơn nữa, ví dụ một AI agent nhiều bước, trả về HTTP 202 Accepted ngay rồi xử lý bất đồng bộ. AnyHook đánh dấu lượt gửi là thành công khi nhận 202.

Phản hồi nào dẫn tới thử lại?

Không phải lỗi nào cũng thử lại được. AnyHook phân loại phản hồi của endpoint để không tốn lần gửi, hay hạn mức compute của bạn, vào những lỗi mà thử lại cũng không sửa được:

Mã trạng tháiHành độngLý do
2xxThành công: lượt gửi hoàn tấtEndpoint của bạn đã nhận sự kiện.
3xxVĩnh viễn: đánh dấu thất bại ngayAnyHook không theo redirect; đặt URL cuối cùng làm URL đích.
408 Request TimeoutThử lại với backoffTimeout tạm thời, có thể thành công ở lần gửi sau.
429 Too Many RequestsThử lại với backoffEndpoint của bạn đang giới hạn tốc độ, cần cho nó thêm thời gian.
4xx (các mã khác: 400, 401, 403, 404, 422…)Vĩnh viễn: đánh dấu thất bại ngayLỗi phía client: request sai định dạng hoặc không được phép. Thử lại cùng payload sẽ không giúp gì.
5xxThử lại với backoffLỗi server, nhiều khả năng là tạm thời.
Lỗi mạng / timeoutThử lại với backoffKhông kết nối được tới endpoint của bạn.
Vì sao 4xx là lỗi vĩnh viễn. 400 Bad Request hay 422 Unprocessable Entity nghĩa là handler của bạn đã từ chối payload, và thử lại cũng không đổi được kết quả. Khi đánh dấu thất bại ngay, bạn được thông báo sớm hơn, không tốn hạn mức compute vào các lần thử lại, và bạn có thể phát lại sự kiện sau khi sửa handler.

Mỗi lần gửi được ghi lại kèm toàn bộ body phản hồi và mã trạng thái. Cả lỗi 4xx vĩnh viễn lẫn lỗi có thể thử lại đều được tính cho circuit breaker (cơ chế ngắt mạch): 20 lần thất bại liên tiếp thuộc bất kỳ loại nào sẽ làm nó ngắt.

Thông báo khi thất bại

AnyHook gửi email cảnh báo ở những ngưỡng cụ thể thay vì cho từng sự kiện, để hộp thư của bạn không bị quá tải:

  • Lần thất bại thứ 1: cảnh báo ngay, kèm mã trạng thái và đích.
  • Lần thất bại liên tiếp thứ 5: cảnh báo leo thang, kèm gợi ý.
  • Lần thất bại liên tiếp thứ 20: circuit breaker ngắt (xem bên dưới), gửi cảnh báo circuit breaker.

Một email ở mỗi ngưỡng nêu trên, rồi một email ở mỗi bậc của circuit breaker (mỗi lần tạm dừng và lần vô hiệu hóa), không bao giờ gửi một email cho mỗi sự kiện. Chỉ cần một lượt gửi thành công là bộ đếm được đặt lại.

Circuit breaker

Nếu AnyHook phát hiện 20 lần gửi thất bại liên tiếp tới cùng một đích (kể cả 4xx vĩnh viễn), circuit breaker ngắt và bắt đầu leo thang theo từng bậc:

Lần ngắtThời gian tạm dừngĐiều gì xảy ra
110 phútTạm dừng đích, gửi email
230 phútTạm dừng đích, gửi email
32 giờTạm dừng đích, gửi email
424 giờTạm dừng đích, gửi email
5Vĩnh viễnVô hiệu hóa đích, gửi email

Cách hoạt động

  • Chiều nhận vẫn hoạt động. URL nhận của bạn luôn hoạt động, sự kiện từ Stripe, GitHub, v.v. vẫn được nhận và lưu. Chỉ chiều gửi đi tới đích của bạn bị tạm dừng.
  • Sau mỗi lần tạm dừng hết hạn, AnyHook gửi một lượt thăm dò half-open: sự kiện tiếp theo được dùng để thử đích của bạn. Nếu thành công, mạch đóng lại và mọi thứ được đặt lại.
  • Chỉ một lần thành công là mọi thứ được đặt lại: cả số lần thất bại liên tiếp lẫn số lần ngắt đều trở về không.
  • Sự kiện nhận được trong lúc tạm dừng được lưu với trạng thái circuit_open và có thể được phát lại khi đích của bạn khôi phục.
  • Bạn nhận email thông báo ở mỗi bậc leo thang, kèm URL đích, số lần thất bại và thời gian tạm dừng.
  • Nếu đích bị vô hiệu hóa (lần ngắt thứ 5), bạn phải tự bật lại nó trong dashboard.

Cách leo thang từng bậc này cho server của bạn thời gian để khôi phục, đồng thời không để các lần thử lại tới một endpoint đã chết tiêu hao hạn mức compute của bạn.

Nhật ký sự kiện

Mọi sự kiện tới được URL nhận của bạn đều được ghi lại, dù việc gửi nó diễn ra thế nào. Nhật ký bắt đầu từ lúc bạn trỏ nhà cung cấp về AnyHook: một sự kiện đã thất bại trước thời điểm đó chỉ còn ở phía nhà cung cấp (với Stripe, xem cách phát lại một webhook). Mỗi mục nhật ký gồm:

  • Toàn bộ header và body đến (đúng như nhận từ bên gửi)
  • Trạng thái gửi: queued → success / retrying / failed / rejected / circuit_open / quota_exceeded. rejected nghĩa là AnyHook đã từ chối sự kiện (xác minh chữ ký thất bại, hoặc URL đích bị chặn); sự kiện chưa từng được gửi đi và không thể phát lại.
  • Mã và body phản hồi HTTP của đích (theo từng lần gửi)
  • Độ trễ tính bằng mili giây
  • Số lần gửi và thời điểm thử lại tiếp theo
  • Sự kiện có phải là một lần phát lại hay không (is_replay: true)

Thời gian lưu sự kiện tùy theo gói. Free: 3 ngày, phát lại được trong suốt thời gian đó, sau đó bị xóa. Pro: 1 năm: phát lại được trong 30 ngày, sau đó nằm trong kho lưu trữ mã hóa, chỉ đọc. Scale: 7 năm: phát lại được trong 90 ngày, sau đó nằm trong kho lưu trữ mã hóa, chỉ đọc. Enterprise: linh hoạt.

App đang tạm dừng sẽ không lưu gì. Khi một App đang tạm dừng (is_active: false), URL nhận của nó trả về 202 và không lưu gì, nên nhà cung cấp không vô hiệu hóa endpoint nhưng các sự kiện thì mất hẳn. Bật lại App trước khi lưu lượng bạn cần đổ về.

Phát lại trên dashboard

Mọi sự kiện trong nhật ký đều có thể được phát lại từ dashboard bằng một cú bấm. Một lần phát lại gửi payload đã lưu, nguyên từng byte, tới đích mà sự kiện đã được gửi tới, hoặc tới URL mới của đích đó nếu bạn đã đổi nó từ đó tới nay.

  • Vào Dashboard → Events
  • Tìm sự kiện và bấm Replay
  • Một sự kiện mới được tạo với is_replay: true, liên kết với sự kiện gốc
Hạn mức. Phát lại không bị tính vào hạn mức sự kiện hằng tháng của bạn, trừ lần phát lại một sự kiện được tạm giữ (quota_exceeded), được tính một lần như một sự kiện mới. Một lần phát lại tới code của App được tính là một lượt chạy code.

Phát lại qua API (một sự kiện)

POST/api/v1/events/{id}/replayPhát lại một sự kiện theo ID

Đẩy payload gốc của sự kiện lên QStash để gửi đi lần nữa. Sự kiện mới được liên kết với sự kiện gốc qua replay_of.

curl -X POST https://anyhook.net/api/v1/events/{event_id}/replay \
  -H "Authorization: Bearer {api_key}"

Trả về 200 kèm ID của sự kiện mới khi thành công. Trả về 410 Gone nếu sự kiện đã quá thời hạn phát lại.

Sự kiện chưa gửi được

GET/api/v1/apps/{slug}/events/undeliveredLiệt kê các sự kiện không gửi được

Trả về các sự kiện có trạng thái failed, circuit_open hoặc quota_exceeded.

Tham sốKiểuMặc địnhMô tả
sinceISO 860124 giờ trướcChỉ các sự kiện được tạo sau dấu thời gian này
limitinteger100Số sự kiện tối đa trả về (1–200)
curl "https://anyhook.net/api/v1/apps/stripe-prod/events/undelivered?since=2026-04-11T00:00:00Z&limit=50" \
  -H "Authorization: Bearer {api_key}"
{
  "count": 3,
  "events": [
    { "id": "evt_abc", "status": "failed", "created_at": "..." },
    ...
  ]
}

Phát lại hàng loạt

POST/api/v1/apps/{slug}/replay-failedPhát lại các sự kiện thất bại của một App

Phát lại các sự kiện thất bại khớp điều kiện, cũ nhất trước, tối đa theo giới hạn mỗi lệnh gọi. Sự kiện đã có lần phát lại, hoặc đã quá thời hạn phát lại, được bỏ qua.

Tham số bodyKiểuMặc địnhMô tả
sinceISO 860124 giờ trướcChỉ phát lại các sự kiện được tạo sau dấu thời gian này
limitinteger50Số sự kiện tối đa được phát lại (1–100)
curl -X POST https://anyhook.net/api/v1/apps/stripe-prod/replay-failed \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{"since": "2026-04-11T00:00:00Z", "limit": 100}'
{
  "replayed": 3,
  "events": [
    { "original_id": "evt_abc", "new_id": "evt_xyz" },
    ...
  ]
}
Giới hạn phát lại hằng ngày. Free: 100/ngày, Pro: 500/ngày, Scale: 1.000/ngày, Enterprise: không giới hạn.

Phát triển trên máy local

AnyHook dùng được ngay cho phát triển trên máy local, không cần chế độ đặc biệt hay CLI nào. Có hai cách:

1. Chuyển tiếp trực tiếp qua tunnel

Đặt đích của App là một URL tunnel trỏ về máy local của bạn. Nhà cung cấp tunnel nào cũng được:

# ngrok
ngrok http 3000
# → https://abc123.ngrok.io

# cloudflared
cloudflared tunnel --url http://localhost:3000

# localhost.run
ssh -R 80:localhost:3000 nokey@localhost.run

Sao chép URL tunnel vào đích của App trong dashboard. AnyHook chuyển tiếp webhook tới đó giống hệt mọi endpoint khác: thử lại, ghi nhật ký và header chữ ký đều hoạt động như nhau.

Mẹo. Giữ cho nhà cung cấp trỏ về URL nhận production (https://in.anyhook.net/you/app) trong lúc phát triển. Bạn có các sự kiện thật được ghi vào nhật ký, và có thể đổi đích qua lại giữa tunnel và production mà không cần động tới Stripe.

2. Ghi lại và phát lại về localhost

Để các sự kiện production thật tích lũy trong AnyHook, rồi phát lại bất kỳ sự kiện nào về môi trường local của bạn khi cần. Không cần tự kích hoạt sự kiện test, test với payload thật, bao nhiêu lần giới hạn phát lại hằng ngày của bạn cho phép.

  • Đổi đích sang URL tunnel của bạn
  • Mở Dashboard → Events và bấm Replay trên một sự kiện bất kỳ
  • Hoặc dùng API: POST /api/v1/events/{id}/replay
  • Payload gốc được gửi nguyên từng byte tới đích hiện tại của bạn

Cách này đặc biệt hữu ích khi debug các trường hợp biên: tìm sự kiện gây lỗi trong log production, phát lại nó trên máy local, rồi sửa tới khi handler chạy đúng.

Phát lại không bị tính vào hạn mức. Phát lại không bị tính vào hạn mức sự kiện hằng tháng của bạn. Phát lại dùng hạn mức compute và giới hạn phát lại hằng ngày (100 trên gói Free, 500 trên gói Pro, 1.000 trên gói Scale).

Mã hóa khi lưu trữ

Mọi dữ liệu payload webhook đều được mã hóa khi lưu trữ bằng AES-256-GCM trước khi tới cơ sở dữ liệu. Bao gồm header đến, body của request và signing secret của từng đích.

Mã hóa dùng một khóa riêng (AES_SECRET_KEY), được lưu tách biệt với thông tin đăng nhập cơ sở dữ liệu. Một vụ rò rỉ cơ sở dữ liệu mà không có khóa mã hóa chỉ thu được bản mã: không có dữ liệu webhook dạng rõ, không có signing secret, không có thông tin khách hàng.

Những gì được mã hóa

  • Header và body của request webhook (mọi sự kiện)
  • Signing secret của nhà cung cấp (webhook secret của Stripe, v.v.)
  • Signing secret AnyHook của từng đích
Không có secret dạng rõ nào trong cơ sở dữ liệu. Mọi secret và mọi payload webhook đều được mã hóa AES-256-GCM trước khi lưu. Kể cả khi có toàn quyền truy cập cơ sở dữ liệu, kẻ tấn công cũng chỉ thấy bản mã.

Xác minh tại edge

AnyHook xác minh chữ ký webhook tại edge của Cloudflare, trước khi sự kiện vào hàng đợi. Khi một App đã có signing secret, request thiếu chữ ký hoặc có chữ ký không hợp lệ bị từ chối bằng mã 401 và không bao giờ tốn hạn mức của bạn; khi chưa có secret, request được chuyển tiếp mà không được xác minh.

Chữ ký được nhận diện tự động dựa trên header. Khi bạn tạo App và chọn loại nguồn, AnyHook lưu signing secret (mã hóa khi lưu trữ) và dùng đúng thuật toán của nhà cung cấp đó.

Request không hợp lệ. Request không qua được bước xác minh chữ ký bị từ chối với HTTP 401 và không bao giờ tới đích của bạn. Trang App đếm số request này, và 5 request đầu tiên mỗi ngày được giữ trong nhật ký sự kiện, kèm header và body.

Nguồn chưa được hỗ trợ hoặc tùy chỉnh

Nếu nguồn webhook của bạn không có trong danh sách được hỗ trợ, chọn Generic khi tạo App. AnyHook sẽ:

  • Nhận và đưa vào hàng đợi mọi POST gửi đến, không kiểm tra chữ ký ở chiều vào.
  • Chuyển tiếp body nguyên từng byte cùng các header gốc của nhà cung cấp tới đích của bạn, kèm một AnyHook-Signature mới ở mỗi lần gửi. Các header hạ tầng được thêm phía trước edge của chúng tôi (cf-* và các header tương tự) bị loại bỏ.
  • Gắn header AnyHook-Signature để endpoint của bạn vẫn xác minh được request đã đi qua AnyHook.

App loại Generic không kiểm tra chữ ký ở chiều vào, dù bạn đặt secret nào. AnyHook-Signature chứng minh request đã đi qua AnyHook, chứ không chứng minh ai đã gửi nó tới AnyHook, nên nếu cần điều đó, giữ phần kiểm tra của riêng bạn trên header gốc của bên gửi (được chuyển tiếp nguyên vẹn). Nếu bên gửi ký theo Standard Webhooks (webhook-id, webhook-timestamp, webhook-signature, như OpenAI và Replicate), chọn nguồn đó thay vào: request sẽ được xác minh tại edge.

AnyHook-Signature

Mọi request được chuyển tiếp đều kèm header AnyHook-Signature, một chữ ký HMAC-SHA256 kèm dấu thời gian, tạo bằng signing secret của đích.

AnyHook-Signature: t=1712934567,v1=5257a869...

Để xác minh trên server của bạn:

// Node.js / TypeScript
import crypto from "crypto";

function verifyAnyHook(header: string, body: string, secret: string): boolean {
  const [tPart, vPart] = header.split(",");
  const timestamp = Number(tPart?.replace("t=", ""));
  // Re-signed on every attempt, so a genuine signature is never older than this.
  if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
  const signature = Buffer.from(vPart?.replace("v1=", "") ?? "", "hex");

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${body}`)
    .digest();

  // timingSafeEqual throws on a length mismatch, so check that first.
  return signature.length === expected.length && crypto.timingSafeEqual(signature, expected);
}

Signing secret được tạo riêng cho từng đích và có trong dashboard tại App → Destinations → Signing secret. Bạn có thể xoay vòng nó bất cứ lúc nào qua API.

Header tùy chỉnh

Bạn có thể gắn header tùy chỉnh vào request gửi đi, riêng cho từng đích. Các trường hợp dùng phổ biến:

  • Authorization: Bearer ...: xác thực với API của bạn
  • x-api-key: ...: xác thực bằng API key
  • Bất kỳ header nào endpoint của bạn yêu cầu

Giá trị header tùy chỉnh được mã hóa khi lưu trữ và không bao giờ lộ ra trong nhật ký sự kiện. Cấu hình chúng trong App → Destinations → Custom headers.

Header gốc được giữ nguyên. Các header gốc của nhà cung cấp (Stripe-Signature, v.v.) được chuyển tiếp nguyên từng byte cùng với header tùy chỉnh của bạn và AnyHook-Signature, nên bước kiểm tra bằng SDK hiện có của bạn vẫn qua ở lần gửi đầu tiên. Các lần thử lại và phát lại mang dấu thời gian gốc của nhà cung cấp, mà SDK của Stripe mặc định từ chối sau 5 phút: kiểm tra AnyHook-Signature thay vào, vì header này được ký mới ở mỗi lần gửi.

Kiểm soát gửi

Ba thiết lập tùy chọn trên mỗi đích, đều có từ gói Pro trở lên. Đặt chúng trong App → Destinations → Delivery controls, hoặc bằng PATCH /api/v1/apps/{slug} trên đối tượng đích. Tài khoản Free giữ các thiết lập đã đặt khi còn trả phí, nhưng không thay đổi được.

{
  "destinations": [{
    "url": "https://api.example.com/hooks/stripe",
    "event_types": ["invoice.*", "checkout.session.completed"],
    "rate_limit": 20,
    "delay_seconds": 5
  }]
}

Bộ lọc loại sự kiện

event_types là một danh sách pattern; đích chỉ nhận những sự kiện có loại khớp với một trong số đó. * khớp với bất kỳ chuỗi ký tự nào, nên invoice.* là mọi sự kiện invoice còn checkout.session.completed là đúng một loại. So khớp không phân biệt hoa thường, trên toàn bộ tên loại. Với hai đích, bạn có thể chia sự kiện theo loại: sự kiện thanh toán tới một service, mọi thứ khác tới service còn lại.

  • Được quyết định tại edge, sau bước xác minh chữ ký và trước bộ đếm hạn mức: sự kiện bị lọc không được lưu, không được gửi đi và không bị tính.
  • Bên gửi nhận 200 {"filtered":true,"event_type":"…"}, thứ mà Stripe và Shopify hiển thị trong nhật ký webhook của chính họ.
  • Loại sự kiện được lấy từ đâu tùy theo nhà cung cấp: một header với GitHub, Shopify, WooCommerce và Lemon Squeezy; trường type trong body với Stripe, Clerk, Resend, Vercel và các bên gửi kiểu Svix; event_type với Paddle và PayPal. Đó cũng là giá trị mà nhật ký sự kiện hiển thị trong cột Type.
Loại không xác định luôn được gửi đi. Một sự kiện mà AnyHook không đọc được loại (body generic không có trường type, một lệnh ping của nhà cung cấp) được gửi tới mọi đích, bất kể bộ lọc. Cam kết của dịch vụ trung chuyển là không có gì bị mất; một bộ lọc nuốt mất sự kiện không xác định sẽ phá vỡ cam kết đó.

Giới hạn tốc độ

rate_limit giới hạn số lượt gửi tới một đích ở mức đó mỗi giây (từ 1 tới 1.000). Đây là giới hạn tốc độ (rate limit) do bạn đặt. Sự kiện vượt giới hạn chờ trong hàng đợi và được gửi đi theo thứ tự khi có chỗ; không sự kiện nào bị bỏ, và các lần thử lại cũng tính vào cùng giới hạn đó. Dùng nó khi một bên gửi có thể dồn dập nhanh hơn mức server của bạn chịu được: một đợt cập nhật sản phẩm hàng loạt trên Shopify, một làn sóng gia hạn subscription của Stripe đầu tháng, một lần push vào monorepo GitHub kéo theo nhiều sự kiện CI.

Phát lại hàng loạt cũng tuân theo giới hạn này, và đó chính là lúc nó quan trọng nhất: phát lại 500 sự kiện thất bại với tốc độ 20 sự kiện mỗi giây là 25 giây lưu lượng đều đặn, thay vì một đợt tăng vọt rồi lại thất bại.

Trì hoãn gửi

delay_seconds giữ lại lần gửi đầu tiên (từ 1 giây tới 1 ngày). Lý do thường gặp là webhook của bên gửi chạy đua với chính API của họ: checkout.session.completed của Stripe có thể tới trước khi trang thanh toán thành công của bạn ghi xong đơn hàng, và trì hoãn năm giây là đủ để cuộc đua đó không còn xảy ra. Phát lại không bao giờ bị trì hoãn; lịch thử lại sau lần gửi đầu tiên giữ nguyên.

Cảnh báo qua Slack / Discord

Từ gói Pro trở lên, mọi cảnh báo AnyHook gửi qua email cũng được đăng tới một incoming webhook mà bạn đặt một lần trong Settings → Notifications: lượt gửi thất bại (lần thứ 1 và lần thứ 5 liên tiếp), circuit breaker ngắt và vô hiệu hóa đích, các đợt từ chối dồn dập và lúc chúng kết thúc, cùng các thông báo hạn mức ở mức 50% / 90% / 100%. Khi bạn lưu URL, AnyHook đăng một tin nhắn test để bạn biết nó đã tới nơi. Slack, Discord, Mattermost, Rocket.Chat và connector incoming webhook của Teams đều dùng được; payload là {"text": …} (hoặc content với Discord). Tắt “Email on failure” chỉ tắt email; kênh chat vẫn tiếp tục nhận cảnh báo.

Code handler

Trên gói Pro và Scale, một App có thể chạy code của riêng bạn trên mọi sự kiện nó nhận được, trong một sandbox (môi trường cô lập), không cần server riêng. Lưu code sẽ cho App một đích chạy code đó, nên thử lại, phát lại, circuit breaker và nhật ký sự kiện hoạt động như với mọi đích khác, và output console cùng giá trị trả về của mỗi lượt chạy chính là response của lượt gửi đó. Viết code trong mục Code của App hoặc bằng các endpoint bên dưới. Template cho các tác vụ thường gặp có tại /code-templates.md, và việc chuyển từ Pipedream có hướng dẫn riêng.

export default async function (event, { env, store }) {
  // event: { method, headers, body, raw_body, delivery_id, attempt }
  console.log(event.body);
}
  • Sự kiện. body là JSON đã parse, các trường form (urlencoded, hoặc các trường text của một form multipart) hoặc text thô, còn raw_body là text đúng như khi nhận. method luôn là POST; query string, path và IP của bên gọi không được giữ lại. delivery_id là ID X-AnyHook-Delivery của sự kiện, còn attempt đếm từ 1, và bằng 0 ở lượt chạy test.
  • Thất bại. Ném lỗi (throw) làm lượt gửi thất bại, và lượt gửi đó được thử lại theo số lần gửi của gói (Lịch thử lại). Lần thử lại của một sự kiện đã chạy thành công sẽ không chạy lại code, nhưng một lượt chạy thất bại sau khi một lệnh gọi đã đi qua thì có, nên hãy dùng delivery_id làm idempotency key cho những lệnh gọi như vậy khi API cho phép.
  • Các step của Pipedream. export default defineComponent({ async run({ steps, $ }) {} }) cũng chạy được: steps.trigger.event là sự kiện ở trên, $.export và $.flow.exit hoạt động, mọi prop data_store là kho dữ liệu của App, $auth.field của một app prop đọc secret APP_FIELD, và mọi prop khác đọc secret mang tên của nó (channelId đọc CHANNEL_ID). Hướng dẫn chuyển từ Pipedream liệt kê những gì không mang sang được.
  • Package npm. Những gì code import sẽ được cài khi bạn lưu, ghim phiên bản theo cách của Pipedream (import axios from "axios@1.7.9"), tối đa 20 package. Một lần lưu có cài package mới có thể mất vài phút; nếu cài thất bại thì không có gì được lưu.
  • Secret. Đọc qua env.NAME hoặc process.env.NAME, tối đa 20 secret, tên gồm chữ cái, chữ số và dấu gạch dưới, không bắt đầu bằng chữ số. PATH, HOME, NODE_OPTIONS, NODE_PATH, PWD và các tên bắt đầu bằng ANYHOOK_ thuộc về runtime, và mỗi giá trị dài tối đa 4.000 ký tự. Giá trị không bao giờ rời khỏi server.
  • Kho dữ liệu. store.get, set (kèm { ttl } tính bằng giây), has, delete, keys, entries và clear giữ trạng thái giữa các sự kiện, mỗi App một kho: tối đa 1.000 key, mỗi key dài tối đa 256 ký tự, mỗi giá trị tối đa 64.000 byte ở dạng JSON. Một lần import nhận {"entries": {key: value}}, ghi đè các key đã có và đặt chúng không kèm TTL.
  • Lịch hẹn giờ. schedule gồm năm trường cron, theo UTC trừ khi có tiền tố CRON_TZ=Area/City. Mỗi lần kích hoạt sẽ post một sự kiện anyhook.schedule tới URL của chính App, nên nó được tính là một lượt chạy và một sự kiện, và lịch chỉ kích hoạt khi code đang bật. Nguồn của App phải là generic hoặc auto, không có signing secret: dành cho code chạy theo lịch một App riêng.
  • Email. Mọi App cũng nhận mail tại {user-slug}.{app-slug}@anyhook.net, dưới dạng sự kiện email.received với body gồm from, to, subject, message_id, date, size và thư MIME ở trường raw. Giống lịch hẹn giờ, nó cần một App có nguồn generic hoặc auto, không có signing secret, và mail vượt giới hạn payload của gói sẽ bị trả lại.
  • Trang web và form. Một trang có thể fetch() URL nhận: edge trả lời CORS preflight, và mọi response đều kèm Access-Control-Allow-Origin: *. Một form HTML thông thường cần method="post". Thêm ?redirect=https://your-site.com/thanks vào action của form, và một sự kiện đã được lưu sẽ chuyển người truy cập tới đó bằng mã 303, miễn là địa chỉ đó thuộc trang web đã gửi form; nếu không, người truy cập thấy một trang ngắn “Received”. Các trường text nằm trong event.body; file tải lên sẽ không còn.
  • Giới hạn. Code tối đa 64.000 byte, và một lượt chạy được kéo dài 105 giây trên gói Pro và 285 giây trên gói Scale. Mỗi lượt chạy giữ lại 16.000 ký tự cuối cùng của output console. Số lượt chạy mỗi tháng nằm trong bảng gói, và mọi lượt chạy đều được tính, kể cả thử lại, chạy test, phát lại và các lần lịch hẹn giờ kích hoạt. Không có phí vượt hạn mức: khi hết số lượt chạy của tháng, một lượt gửi tới code sẽ nhận 429 và được tạm giữ (circuit_open), không được thử lại, sẵn sàng để phát lại khi sang tháng mới hoặc khi gói được nâng cấp.
  • Test, bật và tắt. POST .../code/test chạy code đã lưu trên sự kiện mới nhất đã lưu của App, trên một event_id, hoặc trên một body bạn truyền vào, rồi trả về log, giá trị trả về và thời gian chạy. Nó được tính là một lượt chạy và không gửi gì đi, nhưng những gì chính code gửi đi thì vẫn xảy ra. Sự kiện chỉ chạy code khi code đang bật. Dashboard lưu code mới ở trạng thái tắt, còn PUT .../code lưu phiên bản đầu tiên ở trạng thái bật, trừ khi bạn truyền "enabled": false; các lần lưu sau giữ nguyên trạng thái hiện tại. Trong lúc một đích khác vẫn đang xử lý cùng các sự kiện, như khi đang chuyển đổi, lưu với "enabled": false và bật code sau khi bạn gỡ đích kia. Mỗi thay đổi bật/tắt cần tới một phút để tới mọi vị trí edge.
GET/api/v1/apps/{slug}/codeCode, tên các secret, lịch hẹn giờ và package, sự kiện có tới code hay không, và số lượt chạy đã dùng trong tháng này
PUT/api/v1/apps/{slug}/codeLưu code, secret (gộp vào; null để xóa một secret), lịch hẹn giờ (null để xóa) và enabled
DELETE/api/v1/apps/{slug}/codeXóa code, các secret và lịch hẹn giờ của nó
POST/api/v1/apps/{slug}/code/testChạy code đã lưu ngay bây giờ, trên một sự kiện đã lưu hoặc một body bạn truyền vào
GET/api/v1/apps/{slug}/storeMọi key còn hiệu lực trong kho dữ liệu của App
POST/api/v1/apps/{slug}/storeImport key: {"entries": {key: value}}
DELETE/api/v1/apps/{slug}/storeXóa một key bằng ?key=, hoặc xóa mọi key

Xác thực

Mọi request API đều cần một Bearer token trong header Authorization:

Authorization: Bearer ahk_live_xxxxxxxxxxxxxxxx

Tạo API key tại Dashboard → Settings → API Keys. Key gắn với tài khoản của bạn, cho quyền đầy đủ hoặc chỉ đọc (bạn chọn khi tạo), và có thể thu hồi bất cứ lúc nào. Một client kết nối qua đăng nhập MCP (anyhook.net/mcp) có thể gọi cùng các endpoint bằng OAuth access token của nó, trong phạm vi quyền bạn đã cho phép.

Giới hạn tốc độ. Lệnh gọi API và MCP bị giới hạn theo từng tài khoản mỗi ngày UTC: 300 trên gói Free, 1.000 trên gói Pro, 3.000 trên gói Scale. Khi vượt giới hạn, bạn sẽ nhận 429 Too Many Requests kèm header Retry-After cho tới 00:00 UTC.

AI agent (MCP)

AnyHook là một MCP server tại https://anyhook.net/mcp, nên một agent có thể tạo App, đọc nhật ký sự kiện và phát lại các sự kiện thất bại cho bạn, và trên gói Pro và Scale, deploy và test code của một App (anyhook_code_deploy, anyhook_code_test, anyhook_code_get). Claude Code, claude.ai, Codex và VS Code kết nối bằng URL và đăng nhập qua trình duyệt, không cần sao chép key: các bước cho từng client.

Cursor, Windsurf và Claude Desktop dùng key. Tạo một key tại Dashboard → Settings → API Keys, nơi hiển thị đoạn cấu hình này với key của bạn đã điền sẵn:

{
  "mcpServers": {
    "anyhook": {
      "command": "npx",
      "args": [
        "-y",
        "anyhook-mcp"
      ],
      "env": {
        "ANYHOOK_API_KEY": "ahk_live_xxxxxxxxxxxxxxxx"
      }
    }
  }
}

Cursor: Settings → MCP → Add new MCP server, hoặc ~/.cursor/mcp.json. Windsurf: ~/.codeium/windsurf/mcp_config.json. Claude Desktop: Settings → Developer → Edit Config. Các tool code cần anyhook-mcp 0.4.0 trở lên, bản mà npx -y tải về, hoặc server tại https://anyhook.net/mcp.

Sau đó hỏi nó: “Tạo một App AnyHook cho webhook Stripe của tôi và cho tôi URL của nó.” Agent có thể đọc toàn bộ nội dung này trong /llms.txt.

App

GET/api/v1/appsLiệt kê mọi App
POST/api/v1/appsTạo App mới
GET/api/v1/apps/{slug}Lấy chi tiết một App
PATCH/api/v1/apps/{slug}Cập nhật một App
DELETE/api/v1/apps/{slug}Xóa một App

Tạo một App:

curl -X POST https://anyhook.net/api/v1/apps \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe Production",
    "source": "stripe",
    "destination_url": "https://api.example.com/webhooks"
  }'
{
  "id": "app_xxxxx",
  "name": "Stripe Production",
  "slug": "stripe-production",
  "inbound_url": "https://in.anyhook.net/you/stripe-production",
  "destinations": [
    { "url": "https://api.example.com/webhooks", "enabled": true }
  ]
}

Kết nối Stripe hoặc GitHub

POST/api/v1/connectĐăng ký URL của một App tại nhà cung cấp bằng key của chính nhà cung cấp đó

Đưa vào một Stripe key hoặc GitHub token, AnyHook sẽ thêm URL nhận của App làm webhook tại nhà cung cấp, lưu signing secret mà nhà cung cấp trả về, và kiểm tra chữ ký ngay từ sự kiện đầu tiên. Nhà cung cấp được xác định từ key. Key chỉ được dùng cho các lệnh gọi này và không được lưu. Không có app thì một App mới được tạo; chạy lại sẽ thay thế đăng ký cũ thay vì thêm một đăng ký thứ hai.

Quyền tối thiểu là đủ: một restricted key của Stripe với Webhook Endpoints: Write, hoặc một fine-grained token của GitHub với Webhooks: Read and write trên repository.

# Stripe: a restricted key, connecting an existing app
curl -X POST https://anyhook.net/api/v1/connect \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{ "key": "rk_live_...", "app": "payments" }'

# GitHub: name the repository (or an organization); no app, so one is created
curl -X POST https://anyhook.net/api/v1/connect \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{ "key": "github_pat_...", "target": "acme/api" }'

GitHub gửi một ping có chữ ký ngay khi hook được tạo, nên sự kiện đầu tiên xuất hiện trong vài giây. Với các nhà cung cấp khác: tạo App bằng POST /api/v1/apps rồi dán URL nhận của nó vào phần cài đặt webhook của họ.

Sự kiện

GET/api/v1/eventsLiệt kê tóm tắt sự kiện, mới nhất trước (không có payload)
GET/api/v1/events/{id}Lấy chi tiết sự kiện, gồm toàn bộ payload

Tham số truy vấn khi liệt kê:

Tham sốKiểuMô tả
app_slugstringLọc theo slug của App
statusstringLọc theo trạng thái (queued, success, retrying, failed, rejected, circuit_open, quota_exceeded)
limitintegerSố kết quả tối đa (mặc định 50, tối đa 200)

Phát lại (API)

POST/api/v1/events/{id}/replayPhát lại một sự kiện

Xem Phát lại qua API ở trên để biết đầy đủ chi tiết và ví dụ.

Chưa gửi được (API)

GET/api/v1/apps/{slug}/events/undeliveredLiệt kê các sự kiện thất bại/chưa gửi được

Xem Sự kiện chưa gửi được ở trên để biết đầy đủ chi tiết và ví dụ.

Phát lại hàng loạt (API)

POST/api/v1/apps/{slug}/replay-failedPhát lại mọi sự kiện thất bại của một App

Xem Phát lại hàng loạt ở trên để biết đầy đủ chi tiết và ví dụ.

Giới hạn của gói

FreePro ($20/tháng)Scale ($90/tháng)Enterprise
Sự kiện/tháng10.00050.000250.000Linh hoạt
Giới hạn hằng ngày1.000Không cóKhông cóKhông có
Số App11050Linh hoạt
Số đích mỗi App32050Linh hoạt
Số lần gửi3510Linh hoạt
Lịch sử3 ngày1 năm7 nămLinh hoạt
Phát lại được trong3 ngày30 ngày90 ngàyTối đa 365 ngày
Timeout gửi60 giây120 giây5 phútLinh hoạt
Payload tối đa512 KB2 MB4 MBTùy chỉnh
Phí vượt hạn mứcKhông có$0.15/1k$0.17/1kTheo hợp đồng
Giới hạn phát lại hằng ngày1005001.000Không giới hạn
Lượt chạy code/thángKhông có3.00012.000Linh hoạt
Thời gian chạy codeKhông có105 giây285 giâyLinh hoạt
Hạn mức compute$0.15 mỗi tháng$3.00 mỗi tháng$11.00 mỗi thángLinh hoạt
Lệnh gọi API và MCP300 mỗi ngày1.000 mỗi ngày3.000 mỗi ngàyLinh hoạt

Xem chi tiết bảng giá →

Mức sử dụng và hạn mức

AnyHook theo dõi mức sử dụng theo tháng dương lịch (ví dụ “2026-04”). Bộ đếm được đặt lại vào đầu mỗi tháng dương lịch.

  • Một cho mỗi lượt gửi: một sự kiện gửi tới hai đích tính là hai, và một sự kiện lớn được tính một lần cho mỗi 64 KB body và header (phần lẻ cũng tính là một). Một sự kiện trên App không có đích được tính một lần. Sự kiện bị bộ lọc bỏ qua và sự kiện không qua được bước xác minh chữ ký không bị tính. Giới hạn hằng ngày của gói Free thì đơn giản hơn: mỗi request được edge chấp nhận tính một lần, dù nó được gửi tới bao nhiêu đích.
  • Chỉ lần gửi đầu tiên: các lần thử lại không bị tính vào hạn mức của bạn.
  • Phát lại không bị tính: các sự kiện có is_replay: true không bị tính phí, trừ lần phát lại một sự kiện được tạm giữ do vượt hạn mức, được tính một lần như một sự kiện mới. Phát lại dùng hạn mức compute và giới hạn phát lại hằng ngày của gói.
  • Hạn mức compute: những gì AnyHook chi cho tài khoản của bạn ngoài lần gửi đầu tiên của mỗi sự kiện, tính theo giá vốn: thử lại, phát lại, lượt chạy code, cài package, các lần lịch hẹn giờ kích hoạt, diễn tập sự cố, và thời gian chờ một đích sau giây đầu tiên (một lần thử lại của sự kiện 1 KB dùng khoảng $0.00002, một lượt chạy code vài giây khoảng $0.0008). Khi hết hạn mức, những thứ đó dừng lại cho tới ngày 1 (UTC): lượt gửi thất bại không được thử lại, phát lại và lượt chạy code không được chấp nhận, lịch hẹn giờ tạm dừng, và một đích chỉ có một giây để trả lời. Sự kiện vẫn tới và vẫn có lần gửi đầu tiên.
  • Lệnh gọi API và MCP: mỗi lệnh gọi bằng API key hoặc từ một MCP client tính là một, và một response lớn hơn 32 KB được tính một lần cho mỗi 32 KB (phần lẻ cũng tính là một); dashboard không bị tính. Khi vượt giới hạn trong ngày, API trả về 429 kèm Retry-After cho tới 00:00 UTC. GET /api/v1/usage (hoặc anyhook_usage qua MCP) hiển thị mọi giới hạn và những gì đã dùng tới nó.
  • Được tạm giữ, không bị bỏ: khi vượt số sự kiện đã bao gồm trong gói (và vượt trần chi tiêu, nếu bạn có đặt), mỗi sự kiện được ghi lại với trạng thái quota_exceeded và không được gửi đi. Nâng cấp hoặc đặt trần, rồi phát lại chúng bằng một cú bấm từ dashboard. Sự kiện được tạm giữ tới tối đa 20% hạn mức, tính từ mức mà trần chi tiêu mua được; vượt mức đó, edge từ chối bằng mã 429 và không lưu gì, để chặn một nguồn gửi mất kiểm soát.
Giới hạn của gói Free. Sự kiện vượt giới hạn hằng ngày (1.000) bị từ chối tại edge. Sự kiện vượt hạn mức hằng tháng (10.000) được tạm giữ thêm tối đa 20% hạn mức đó: chúng xuất hiện trong nhật ký sự kiện với trạng thái quota_exceeded và có thể được phát lại sau khi nâng cấp. Vượt mức đó, chúng bị từ chối như giới hạn hằng ngày. Nâng cấp lên Pro để có phí vượt hạn mức tính theo mức dùng thực tế.
Phí vượt hạn mức của gói trả phí. Gói Pro tính phí vượt hạn mức $0.15/1k sự kiện, gói Scale $0.17/1k, làm tròn lên tới cent gần nhất. Chúng tôi không bao giờ âm thầm bỏ lưu lượng của gói trả phí.

Phí vượt hạn mức

Khi hạn mức hằng tháng hết, phí vượt hạn mức cho phép sự kiện tiếp tục được gửi thay vì được tạm giữ. Chỉ có trên gói Pro và Scale.

Cách hoạt động

  1. Đặt trần chi tiêu hằng tháng trong Settings ($5 / $10 / $20 / $50, hoặc Off).
  2. Khi đã dùng hết hạn mức sự kiện của gói, AnyHook kiểm tra xem phí vượt hạn mức có được bật hay không và trần chi tiêu của bạn đã chạm chưa.
  3. Nếu được phép, sự kiện được gửi bình thường và được gắn nhãn vượt hạn mức.
  4. Đầu tháng sau, phí vượt hạn mức được tính thành một dòng duy nhất trên hóa đơn Stripe của bạn, $0.15 cho mỗi 1.000 sự kiện với gói Pro, $0.17 cho mỗi 1.000 sự kiện với gói Scale (tính theo từng sự kiện, làm tròn lên tới cent), tối đa bằng trần chi tiêu của bạn.

Bật/tắt cho từng App

Mỗi App có một thiết lập phí vượt hạn mức: Inherit default, Enabled hoặc Disabled. Mặc định, App kế thừa thiết lập ở cấp tài khoản. Ghi đè cho từng App trong App Settings → Overage Billing.

Mặc định của tài khoản

Trong Settings → Overage Billing, đặt trần chi tiêu cao hơn Off sẽ bật phí vượt hạn mức cho mọi App đang để Inherit default, kể cả App hiện có; App đang để Disabled vẫn tắt.

Trần chi tiêu là giới hạn cứng. Khi chi phí vượt hạn mức chạm trần, các sự kiện tiếp theo không được gửi đi: mỗi sự kiện được lưu với trạng thái quota_exceeded và phát lại được khi bạn nâng trần hoặc sang tháng mới. Không có gì vượt trần bị tính phí. Sau khi chạm trần, sự kiện được tạm giữ thêm tối đa 20% hạn mức nữa; phần còn lại bị edge từ chối bằng mã 429 và không được lưu.

API key

API key xác thực các request của bạn tới REST API của AnyHook. Quản lý chúng tại Dashboard → Settings → API Keys.

  • Key có tiền tố ahk_live_ cho production.
  • Bạn có thể tạo nhiều key và đặt nhãn cho chúng (ví dụ “CI/CD”, “Agent”).
  • Có thể thu hồi key ngay, key đã thu hồi trả về 401 ở lần dùng tiếp theo.
  • Key chỉ hiển thị một lần khi tạo. Lưu key ở nơi an toàn.
GET/api/v1/api-keysLiệt kê mọi API key (secret được che)
POST/api/v1/api-keysTạo API key mới
PATCH/api/v1/api-keys/{id}/revokeThu hồi một API key

Cần hỗ trợ?

Email cho chúng tôi qua hello@anyhook.net: chúng tôi trả lời mọi tin nhắn. Xem status.anyhook.net để biết tình trạng dịch vụ theo thời gian thực.