Go Webhook Receiver Starter

Một service Go nhỏ nhận webhook cho an toàn: xác thực chữ ký, chống trùng, lưu bền, tự retry, còn code của bạn nằm trong đúng một hàm.

Mua trên Polar: $24 Mua một lần · v0.2.1 · tải ngay qua Polar sau khi thanh toán

Ảnh bìa Go Webhook Receiver Starter
Kết quả của một lần chạy test thật, không phải ảnh dựng.

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_SECRET nhậ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_ATTEMPTS lần, và tự khôi phục những event còn kẹt ở processing do crash.
  • Chạy song song. WORKERS=N worker, và một event không bao giờ đến tay hai worker.
  • Vận hành. server replay cho các event đã chết, server purge cùng RETENTION_DAYS để dọn dòng cũ, /metrics theo đị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.paid củ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ụcKết quả
Event nhận qua HTTP (xác thực, ghi bền, trả 202), kiểu GitHubkhoả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 Webhookskhoảng 9,400 mỗi giây
Từ request đầu tiên đến khi queue rỗng (handler không làm gì), 1 workerkhoả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 workerkhoả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

Mua sản phẩm này

Mua trên Polar: $24

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.