Trong bộ này có gì
Một service hoàn chỉnh, đọc hết trong một buổi chiều, không phải framework. Logic của bạn nằm trong một hàm handle(). Mọi thứ quanh nó đã làm xong và đã test.
sender --POST--> /webhook/{source}
1. read the body (size limit)
2. verify the HMAC signature -> 401 if wrong, nothing stored
3. dedupe by delivery id -> the same event twice is handled once
4. insert into SQLite -> 202 Accepted, fast
worker ----------> handle(event) retries with backoff, dead after N attempts
Tính năng
- Ba kiểu chữ ký. Kiểu GitHub, kiểu Stripe (có cửa sổ chống replay) và Standard Webhooks, chuẩn mà Polar dùng. So sánh trong thời gian không đổi (constant-time).
- Xoay secret.
WEBHOOK_SECRETnhận một danh sách phân tách bằng dấu phẩy, nên lúc xoay secret cả cái cũ lẫn cái mới đều được chấp nhận và không rớt event nào. - Chống trùng. Theo delivery id (với Standard Webhooks, header
webhook-idđược dùng tự động), còn nếu không có thì theo hash của body. Việc này dựa trên ràng buộc unique nên vẫn đúng sau khi restart. - Queue bền. SQLite ở chế độ WAL, request được xác nhận sau đúng một lần commit. Retry với backoff tăng dần, chuyển sang dead-letter sau
MAX_ATTEMPTSlần, và tự khôi phục những event còn kẹt ởprocessingdo crash. - Chạy song song.
WORKERS=Nworker, và một event không bao giờ đến tay hai worker. - Vận hành.
server replaycho các event đã chết,server purgecùngRETENTION_DAYSđể dọn dòng cũ,/metricstheo định dạng Prometheus, và access log dạng JSON có request id, tuyệt đối không chứa payload, header hay secret. - Handler an toàn.
worker.Permanent(err)cất đi những payload không bao giờ xử lý được, không tốn thêm lần retry nào. Nếu service tắt giữa chừng một handler, event quay về queue mà không mất lượt thử. - Ví dụ. Một handler cho
order.paidcủa Polar và một router JSON chung, cả hai đều có test. Kèm Dockerfile và systemd unit.
Thuần Go (không cgo). Phụ thuộc duy nhất ngoài thư viện chuẩn là modernc.org/sqlite.
Số đo
Cách đo: dùng công cụ load test đi kèm (make loadtest) chạy vào binary đã build, trên laptop Apple Silicon, 32 kết nối, payload 512 byte, HTTP thật qua loopback, độ bền mặc định (WAL, synchronous=FULL), 5,000 event mỗi lần chạy, chạy ba lần.
| Hạng mục | Kết quả |
|---|---|
| Event nhận qua HTTP (xác thực, ghi bền, trả 202), kiểu GitHub | khoảng 7,200 đến 9,200 mỗi giây (p50 2.7 ms, p99 khoảng 17 ms) |
| Như trên, với Standard Webhooks | khoảng 9,400 mỗi giây |
| Từ request đầu tiên đến khi queue rỗng (handler không làm gì), 1 worker | khoảng 3,100 đến 3,200 mỗi giây |
| Xả queue, handler ngủ 5 ms (giống một cuộc gọi mạng), 1 / 8 / 32 worker | khoảng 163 / 1,463 / 5,264 mỗi giây |
Chi phí chính nằm ở database, không phải HTTP: mọi truy cập SQLite đi qua một kết nối duy nhất, nên thêm worker chỉ có lợi khi handler của bạn phải chờ thứ gì đó, như một cuộc gọi mạng hay một database khác.
Đã test thế nào
make test chạy go vet và 41 test với race detector. Chúng gồm chữ ký bị sửa, quá hạn, từ tương lai và sai định dạng, test vector chuẩn của Standard Webhooks lấy từ thư viện Go tham chiếu, xoay secret, 401 thì không lưu gì, delivery bị gửi trùng, request quá lớn hoặc sai method, retry và backoff, dead-letter, khôi phục sau crash, thứ tự xử lý, nhiều worker mà không event nào bị xử lý hai lần, replay, purge, và nâng cấp từ database của bản 0.1.0. Binary build từ chính file zip đóng gói cũng được gọi bằng request HTTP thật có ký, cho cả ba kiểu chữ ký, kể cả việc gửi lại đúng delivery đó.
Dành cho ai
Dành cho dev cần nhận webhook từ Polar, Stripe, GitHub hay tương tự, và muốn những phần chán nhưng quan trọng được làm đúng: chữ ký, retry, chống trùng, và một queue sống sót qua crash.
Nó được thiết kế cho một server: một process, một writer SQLite, và mặc định event được xử lý theo thứ tự. Việc giao event là at-least-once, nên handler của bạn cần idempotent.
Câu hỏi hay gặp
Nó có xác thực webhook của Polar không?
Có. Polar dùng Standard Webhooks, chuẩn này được hỗ trợ, và bộ starter có sẵn một handler mẫu cho event đơn hàng của Polar.
Tôi chỉ muốn lấy phần code chữ ký thì được không?
Được. Phần này có riêng, là một module nhỏ webhooksig dưới giấy phép MIT. Xem trang công cụ miễn phí.
Có cần CGO hay trình biên dịch C không?
Không. Driver SQLite là thuần Go.
Dùng bản Go nào?
Go 1.24 trở lên.
Giấy phép thế nào?
Dùng trong dự án của bạn. Không bán lại source như một bộ starter cạnh tranh.
Lịch sử thay đổi
- 0.2.1: nâng driver SQLite lên v1.44.0 (yêu cầu Go 1.24 trở lên).
- 0.2.0: Standard Webhooks, xoay secret, nhiều worker, lệnh replay và purge,
/metrics, access log, ví dụ Polar, có số đo. - 0.1.0: bản đầu tiên.
Bài liên quan
- Webhook signature checks that fail or quietly pass in Go (tiếng Anh)
- A durable webhook retry queue in SQLite (tiếng Anh)
- Webhook idempotency and dedupe for SQLite and Postgres (tiếng Anh)
- Verify Standard Webhooks (Polar) signatures in Go (tiếng Anh)
- Receiving signed webhooks in Go with a durable queue (tiếng Anh)
Mua sản phẩm này
Polar lo thanh toán, thuế và hóa đơn (họ là merchant of record). Thanh toán xong bạn nhận link tải ngay.