Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 5 — Webhook: nhận sự kiện và trả kết quả agent an toàn

Đã đọc 0/11 mục · còn khoảng 3 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Agent hiếm khi chỉ do người dùng bấm nút. Nó thường được kích hoạt bởi sự kiện: tin nhắn mới trên Slack, email đến, thanh toán thành công, comment GitHub. Webhook là cách hệ thống ngoài "gọi" agent của bạn. Bài này chỉ cách nhận webhook đúng và trả kết quả bất đồng bộ.

Webhook đi hai chiều

  • Inbound (bạn nhận): dịch vụ ngoài POST vào endpoint của bạn để kích hoạt agent.
  • Outbound (bạn gửi): agent chạy xong, bạn POST kết quả về endpoint của client/hệ thống khác.

Quy tắc vàng: trả 200 NGAY, xử lý sau

Hầu hết nhà cung cấp webhook (Slack, Stripe, GitHub) timeout 3–10 giây và sẽ gửi lại nếu bạn không trả 2xx kịp. Agent chạy 30 giây → chắc chắn timeout → họ gửi lại → agent chạy nhiều lần. Đúng cách:

@app.post("/webhook/slack")
def slack_webhook(req):
    if not verify_signature(req):        # 1. Xác thực trước
        return Response(status=401)
    event = req.json
    if seen(event["event_id"]):          # 2. Chống trùng
        return Response(status=200)
    queue.enqueue(event)                 # 3. Đẩy vào queue, KHÔNG chạy agent ở đây
    return Response(status=200)          # 4. Trả 200 ngay (<1s)

Nhận nhanh → đẩy vào queue (Bài 4) → worker chạy agent → trả kết quả qua outbound webhook.

Ba việc bắt buộc khi nhận webhook

1. Xác thực chữ ký (signature verification)

Bất kỳ ai cũng có thể POST vào URL của bạn. Kẻ xấu có thể giả sự kiện để kích hoạt agent tốn tiền. Luôn kiểm tra HMAC signature bằng secret dùng chung.

import hmac, hashlib
def verify_signature(req):
    sig = req.headers.get("X-Signature", "")
    mac = hmac.new(SECRET, req.raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, sig)   # so sánh chống timing attack

2. Idempotency theo event id

Webhook thường "at-least-once" → cùng một sự kiện có thể đến nhiều lần. Lưu event_id đã xử lý và bỏ qua bản trùng.

3. Trả nhanh, tách xử lý

Không bao giờ chạy agent ngay trong handler webhook.

Gửi outbound webhook đáng tin

Khi agent xong, bạn POST kết quả về client. Endpoint của họ có thể tạm chết → bạn cần:

  • Retry với backoff (như queue).
  • Ký chữ ký để client tin đây là bạn.
  • Timeout ngắn cho lời gọi outbound.
  • Log delivery để debug khi client bảo "không nhận được".

Khung tư duy: 4 lớp phòng thủ webhook inbound

1. Verify  -> đúng nguồn không?
2. Dedupe  -> đã xử lý event này chưa?
3. Enqueue -> đẩy vào queue, không chạy nặng
4. Ack 200 -> trả nhanh để không bị gửi lại

Các bước triển khai

  • Tạo endpoint riêng cho mỗi nguồn webhook.
  • Cài verify signature bằng hmac.compare_digest (không dùng ==).
  • Lưu bảng processed_events(event_id) để dedupe.
  • Đẩy event vào queue, trả 200 ngay.
  • Với outbound: thêm retry backoff, ký payload, log mỗi lần gửi.
  • Test replay: gửi lại đúng event, xác nhận agent không chạy lần hai.

Sai lầm thường gặp

  • Chạy agent ngay trong handler webhook: bị timeout → nhà cung cấp gửi lại → agent chạy lặp, đốt tiền.
  • Không verify signature: URL public bị lạm dụng để kích hoạt agent giả.
  • Dùng == so sánh chữ ký: dính timing attack; phải dùng compare_digest.
  • Không dedupe theo event id: một sự kiện thật bị xử lý nhiều lần.
  • Đọc body sau khi framework đã parse: hỏng việc tính HMAC trên raw body.
  • Outbound không retry/log: client mất kết quả mà bạn không biết vì sao.
Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi