Menu
ESC

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

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

Đang tải...

Webhooks vs Polling: chọn cách nhận event

API and Technical Fundamentals cho BA Bài 26/60

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn đang là BA cho một sàn thương mại điện tử. Khách hàng đặt hàng, thanh toán qua cổng VNPay. Câu hỏi nghiệp vụ tưởng chừng đơn giản: "Làm sao hệ thống của chúng ta biết khách đã thanh toán thành công?" Đằng sau câu hỏi đó là một quyết định kỹ thuật cực kỳ phổ biến mà BA gặp gần như trong mọi dự án tích hợp: hệ thống của chúng ta nên chủ động đi hỏi (polling), hay để bên kia chủ động báo về (webhook)?

Đây không phải chuyện chỉ dành cho developer. Cách bạn chọn ảnh hưởng trực tiếp đến trải nghiệm người dùng (khách phải đợi bao lâu mới thấy "Đặt hàng thành công"?), đến chi phí hạ tầng (gọi API liên tục tốn tiền), và đến độ phức tạp của requirement bạn phải viết. Khi đọc tài liệu của VNPay, MoMo, hay Stripe, bạn sẽ thấy họ đều dùng từ "IPN" hoặc "webhook callback" — nếu không hiểu khái niệm này, bạn sẽ viết user story thiếu sót và để lọt rủi ro.

Bài này tập trung duy nhất vào việc giúp bạn hiểu thấu hai cách nhận event, biết khi nào dùng cái nào, và biết đặt đúng câu hỏi khi review spec tích hợp. Đây là kiến thức nền tảng mà một BA "biết công nghệ" bắt buộc phải nắm.

Khái niệm cốt lõi

Trước hết cần làm rõ "event" (sự kiện) là gì. Event là một việc đã xảy ra mà hệ thống khác quan tâm: đơn hàng đã thanh toán, kiện hàng đã giao, tài khoản vừa được tạo, file vừa xử lý xong. Vấn đề là: hệ thống A tạo ra event, nhưng hệ thống B mới là bên cần biết. Làm sao thông tin chảy từ A sang B? Có hai mô hình.

Polling — chủ động đi hỏi

Polling nghĩa là hệ thống của bạn (client) định kỳ gọi sang server để hỏi "có gì mới không?". Server trả về trạng thái hiện tại, dù có thay đổi hay không.

Client: GET /api/orders/123/status
Server: { "status": "pending" }

(đợi 5 giây)

Client: GET /api/orders/123/status Server: { "status": "pending" }

(đợi 5 giây)

Client: GET /api/orders/123/status Server: { "status": "paid" } ← cuối cùng cũng đổi

Bạn hình dung như một đứa trẻ ngồi sau xe hỏi đi hỏi lại "Tới chưa ba? Tới chưa ba?". Phần lớn các lần hỏi đều nhận về câu trả lời "Chưa". Đây chính là điểm yếu của polling: đa số request là lãng phí vì không có gì thay đổi.

Polling có hai biến thể:

  • Short polling: gọi đều đặn theo chu kỳ cố định (ví dụ mỗi 5 giây), server trả lời ngay lập tức dù có data hay không.
  • Long polling: client gọi và server giữ kết nối mở, chỉ trả lời khi có thay đổi hoặc khi hết timeout. Cách này giảm số lần gọi vô ích nhưng phức tạp hơn để triển khai.

Webhook — để bên kia chủ động báo về

Webhook đảo ngược hướng. Thay vì bạn đi hỏi, bạn đăng ký một URL với server, và khi event xảy ra, server sẽ chủ động gọi HTTP POST đến URL đó để báo cho bạn. Webhook đôi khi được gọi là "reverse API" hoặc "HTTP callback".

Bước 1 — Đăng ký (một lần):
  Bạn khai báo với cổng thanh toán:
  "Khi có event, hãy gọi đến https://shop.vn/webhooks/payment"

Bước 2 — Khi event xảy ra (server chủ động): Server → POST https://shop.vn/webhooks/payment Body: { "order_id": 123, "status": "paid", "amount": 500000 } Bạn → HTTP 200 OK (xác nhận "tôi đã nhận")

Quay lại ví dụ chuyến xe: thay vì đứa trẻ hỏi liên tục, ba nói "Con cứ ngủ đi, tới nơi ba gọi". Đó là webhook — bạn không cần làm gì cho đến khi có thông báo.

So sánh nhanh hai mô hình

Tiêu chíPollingWebhook
Ai chủ độngClient đi hỏiServer báo về
Độ trễ (latency)Cao (phụ thuộc chu kỳ hỏi)Thấp (gần như tức thì)
Lãng phí tài nguyênNhiều (đa số request rỗng)Ít (chỉ gọi khi có event)
Độ phức tạp clientĐơn giảnCần dựng endpoint công khai để nhận
Yêu cầu hạ tầngKhông cần gì đặc biệtCần URL public, bảo mật, xử lý retry
Phù hợp khiEvent hiếm, hoặc bạn không kiểm soát được serverCần real-time, event nhiều
Một quy tắc ngầm hữu ích: webhook giống như đăng ký nhận tin nhắn báo cước, polling giống như mỗi ngày tự mở app ra xem hóa đơn. Webhook tốt hơn về độ tức thời, nhưng đòi hỏi bạn phải có "số điện thoại" (endpoint) để nhận và phải xử lý được khi tin nhắn đến.

Tình huống thực tế

Tình huống 1 — Cổng thanh toán VNPay tại một shop online

Công ty TMĐT "Tiệm Nhà Mây" (giả định) tích hợp VNPay để nhận thanh toán. Đội ngũ ban đầu chỉ dùng cơ chế return URL: khi khách thanh toán xong, trình duyệt được redirect về website kèm kết quả. Nghe có vẻ đủ, nhưng họ gặp sự cố: nhiều khách thanh toán thành công ở app ngân hàng rồi tắt trình duyệt luôn, hoặc mạng rớt lúc redirect. Kết quả là tiền đã trừ nhưng đơn hàng vẫn treo ở trạng thái "chờ thanh toán". Mỗi ngày có khoảng 30–40 đơn bị lệch, đội CSKH phải đối soát thủ công.

Giải pháp: VNPay cung cấp cơ chế IPN (Instant Payment Notification) — bản chất chính là một webhook. Server VNPay sẽ gọi POST trực tiếp về backend của shop báo kết quả giao dịch, độc lập hoàn toàn với việc trình duyệt khách có quay về hay không. Sau khi bật IPN, tỷ lệ đơn lệch trạng thái giảm gần như về 0.

Bài học rút ra: Với BA, đây là requirement sống còn. Khi viết spec tích hợp thanh toán, đừng chỉ mô tả luồng "khách bấm thanh toán → quay về web". Phải có một acceptance criteria riêng: "Hệ thống cập nhật trạng thái đơn hàng dựa trên webhook/IPN từ cổng thanh toán, không phụ thuộc vào việc người dùng quay lại website." Webhook là nguồn sự thật (source of truth), return URL chỉ là trải nghiệm hiển thị.

Tình huống 2 — Tích hợp đối tác giao vận không hỗ trợ webhook

Một startup logistics ở TP.HCM cần lấy trạng thái vận đơn từ một hãng vận chuyển nội địa cũ. Đáng tiếc, hãng này không hỗ trợ webhook, chỉ có một API tra cứu: GET /tracking/{ma_van_don}. Vậy là không còn lựa chọn nào khác ngoài polling.

Đội kỹ thuật ban đầu cho hệ thống gọi tra cứu mỗi 30 giây cho từng đơn. Với 5.000 đơn đang hoạt động, con số request lên tới hàng triệu lượt mỗi ngày, và hãng vận chuyển... chặn IP của họ vì nghi ngờ tấn công. BA và team phải thiết kế lại: chỉ poll những đơn ở trạng thái "đang giao" (không poll đơn đã hoàn thành), giãn chu kỳ thành 15 phút/lần, và dùng thêm thông tin "khung giờ giao dự kiến" để chỉ poll dày vào đúng khoảng thời gian đó.

Bài học rút ra: Khi buộc phải polling, BA cần đặt câu hỏi về chu kỳ hợp lýgiới hạn phạm vi. Đừng poll cái gì không cần thiết. Và quan trọng: phải hỏi đối tác về rate limit (giới hạn số request) ngay từ giai đoạn khảo sát, đưa vào tài liệu tích hợp. Polling không xấu — nó là giải pháp đúng khi bạn không kiểm soát được hệ thống đối tác và đối tác không cung cấp webhook.

Tình huống 3 — Slack và webhook đồ sộ

Khi tích hợp thông báo nội bộ qua Slack (rất phổ biến ở các công ty công nghệ Việt Nam), bạn dùng Incoming Webhook: Slack cho bạn một URL, mỗi khi hệ thống có sự kiện (deploy xong, có đơn hàng lớn, server lỗi), backend gọi POST tới URL đó và tin nhắn hiện ngay trong kênh. Đây là webhook ở chiều ngược lại — bạn là bên gửi event, Slack là bên nhận.

Một ngân hàng số đã dùng cách này để gửi cảnh báo giao dịch nghi ngờ gian lận vào kênh #risk-alert. Khi hệ thống phát hiện một giao dịch bất thường, webhook bắn cảnh báo trong dưới 2 giây, đội rủi ro xử lý kịp thời. Nếu dùng polling — ví dụ đội rủi ro mở dashboard kiểm tra mỗi 10 phút — thì độ trễ trung bình 5 phút là quá lâu cho nghiệp vụ chống gian lận.

Bài học rút ra: Webhook tỏa sáng khi nghiệp vụ đòi hỏi real-timeđộ trễ thấp là yếu tố sống còn. BA cần nhận diện những use case mà "chậm vài phút là mất tiền/mất khách" để ưu tiên kiến trúc webhook.

Hướng dẫn từng bước

Khi bạn là BA và phải quyết định hoặc viết spec cho việc nhận event, hãy đi theo các bước sau.

Bước 1 — Xác định event và mức độ khẩn cấp. Liệt kê rõ những event cần nhận (đơn đã thanh toán, hàng đã giao...). Với mỗi event, hỏi: "Nếu biết chậm 5 phút thì có sao không?" Nếu chậm gây thiệt hại rõ rệt → nghiêng về webhook. Nếu chậm vài phút vẫn ổn → polling có thể đủ.

Bước 2 — Kiểm tra khả năng của hệ thống đối tác. Đọc tài liệu API của bên kia. Họ có cung cấp webhook không? Nếu có, đây thường là lựa chọn ưu tiên. Nếu không, bạn buộc phải polling. Đây là ràng buộc kỹ thuật mà BA phải ghi nhận sớm.

Bước 3 — Nếu chọn webhook, kiểm tra điều kiện hạ tầng. Webhook yêu cầu hệ thống của bạn có một endpoint công khai (public URL) để bên kia gọi tới. Đặt câu hỏi với team: endpoint này có sẵn chưa? Có chạy được trên môi trường staging để test không (thường cần công cụ như ngrok)? Có firewall chặn không?

Bước 4 — Thiết kế cho trường hợp webhook thất bại. Đây là phần BA hay quên. Mạng có thể rớt, server của bạn có thể đang bảo trì lúc webhook bắn tới. Hỏi: "Nếu chúng ta không nhận được webhook thì sao?" Best practice là webhook + polling dự phòng: webhook để nhanh, cộng thêm một job định kỳ (vài giờ một lần) đối soát những giao dịch còn treo. Đây là pattern an toàn nhất cho nghiệp vụ tiền bạc.

Bước 5 — Quy định bảo mật và xác thực. Vì webhook là endpoint công khai, bất kỳ ai biết URL đều có thể gửi data giả. Spec phải nêu rõ cách xác thực: chữ ký số (signature) trong header, hoặc whitelist IP, hoặc secret token. Ví dụ VNPay yêu cầu kiểm tra vnp_SecureHash để đảm bảo dữ liệu thật sự đến từ VNPay.

Bước 6 — Quy định phản hồi và idempotency. Bên gửi webhook thường gửi lại (retry) nếu không nhận được HTTP 200. Điều này nghĩa là cùng một event có thể đến hai, ba lần. Spec phải yêu cầu hệ thống xử lý "trùng lặp" an toàn — nhận event "đơn 123 đã thanh toán" lần thứ hai không được cộng tiền hai lần. (Khái niệm idempotency này được đào sâu ở Bài 25, ở đây bạn chỉ cần ghi nhận nó là một acceptance criteria bắt buộc.)

Lỗi thường gặp & mẹo

Lỗi 1 — Coi return URL là nguồn sự thật. Như tình huống VNPay, redirect trình duyệt rất dễ thất bại. Luôn dựa vào webhook/IPN ở backend để cập nhật trạng thái thật, return URL chỉ để hiển thị cho khách.

Lỗi 2 — Polling quá dày dẫn đến bị chặn hoặc tốn chi phí. Mỗi request đều tốn tài nguyên và có thể tính tiền. Mẹo: poll càng thưa càng tốt trong giới hạn nghiệp vụ cho phép, và chỉ poll những đối tượng thật sự cần (đơn "đang xử lý"), bỏ qua những đối tượng đã ở trạng thái cuối.

Lỗi 3 — Quên xử lý webhook đến trùng. Bên gửi retry là chuyện bình thường, không phải bug. Nếu hệ thống bạn không chống trùng, một giao dịch có thể bị ghi nhận nhiều lần. Luôn yêu cầu xử lý theo event_id hoặc transaction_id duy nhất.

Lỗi 4 — Không có cơ chế dự phòng khi webhook chết. Webhook nhanh nhưng "bắn một lần là xong" — nếu lỡ thì khó biết. Mẹo vàng cho nghiệp vụ quan trọng: webhook để nhận nhanh, polling/đối soát định kỳ để bắt sót. Hai cơ chế bổ trợ nhau.

Lỗi 5 — Bỏ qua bảo mật endpoint webhook. Endpoint công khai mà không xác thực chữ ký là lỗ hổng nghiêm trọng — kẻ gian có thể gửi "thanh toán thành công" giả. Luôn đưa yêu cầu verify signature vào spec.

Mẹo cho BA khi review spec: Hãy luôn hỏi ba câu — "Event này cần real-time tới mức nào?", "Đối tác hỗ trợ webhook hay buộc phải poll?", và "Nếu cơ chế nhận event thất bại thì chuyện gì xảy ra với nghiệp vụ?". Ba câu này phủ gần hết rủi ro của một tích hợp.

Bài tập thực hành

  • Phân loại tình huống. Với mỗi nghiệp vụ sau, hãy quyết định nên dùng webhook hay polling và giải thích lý do: (a) thông báo khi đơn hàng được giao thành công; (b) cập nhật tỷ giá ngoại tệ hiển thị trên website; (c) cảnh báo khi nhiệt độ kho lạnh vượt ngưỡng; (d) đồng bộ danh sách sản phẩm từ một hệ thống ERP cũ chỉ có API tra cứu.
  • Viết acceptance criteria. Giả sử bạn là BA cho dự án tích hợp MoMo. Hãy viết 4–5 acceptance criteria cho việc nhận kết quả thanh toán qua webhook, bao gồm: xác thực nguồn gửi, xử lý trùng lặp, phản hồi HTTP, và cơ chế dự phòng khi không nhận được webhook.
  • Thiết kế chu kỳ polling. Một hệ thống cần tra cứu trạng thái 2.000 vận đơn từ đối tác có rate limit 1.000 request/phút. Hãy đề xuất một chiến lược polling hợp lý (chu kỳ, phạm vi đơn cần poll) để vừa cập nhật kịp thời vừa không vượt giới hạn.
  • Vẽ sơ đồ luồng. Vẽ sơ đồ sequence cho luồng webhook thanh toán: từ lúc khách bấm thanh toán, cổng xử lý, gửi webhook về backend, đến lúc đơn hàng đổi trạng thái — kèm cả nhánh "webhook thất bại, đối soát định kỳ bù lại".

Tóm tắt

Polling và webhook là hai cách để hệ thống của bạn biết "có chuyện gì đó vừa xảy ra". Polling là bạn chủ động đi hỏi định kỳ — đơn giản nhưng tốn tài nguyên và có độ trễ; phù hợp khi event hiếm hoặc khi đối tác không hỗ trợ webhook. Webhook là bên kia chủ động báo về — nhanh, tiết kiệm, gần real-time; phù hợp khi cần độ trễ thấp, nhưng đòi hỏi endpoint công khai, bảo mật chữ ký và xử lý event trùng.

Là BA, bạn không cần tự code webhook, nhưng bạn phải biết đặt đúng câu hỏi: event cần nhanh tới đâu, đối tác hỗ trợ cơ chế nào, và điều gì xảy ra khi cơ chế nhận event thất bại. Nguyên tắc thực dụng cho các nghiệp vụ tiền bạc và đơn hàng là dùng webhook để nhận nhanh, kèm polling/đối soát định kỳ để bắt những trường hợp lọt lưới. Nắm vững khái niệm này, bạn sẽ viết được những spec tích hợp chặt chẽ, tránh được những lỗi "tiền đã trừ mà đơn vẫn treo" — loại lỗi gây đau đầu nhất trong mọi dự án.