Webhooks và polling: chọn phương pháp tích hợp phù hợp
Webhooks và polling: tìm hiểu cách mỗi phương pháp ảnh hưởng đến độ trễ, lỗi, giới hạn tần suất và các mô hình thử lại/phát lại giúp dữ liệu đồng bộ.

Vấn đề chúng ta đang giải quyết khi đồng bộ dữ liệu là gì?
"Đồng bộ" nghe có vẻ như “hiển thị cập nhật nhanh,” nhưng công việc thực sự khó hơn: khiến hai hệ thống đồng ý về điều gì là đúng, ngay cả khi thông điệp đến muộn, bị trùng, hoặc bị thiếu.
Với webhooks và polling, điểm khác biệt là cách bạn biết có thay đổi.
Webhook là cơ chế push. Hệ thống A gọi endpoint của bạn khi một sự kiện xảy ra (ví dụ: “invoice paid”). Polling là pull. Hệ thống của bạn hỏi hệ thống A theo lịch trình: “có gì mới kể từ lần trước không?”
Giữ các hệ thống đồng bộ thường có nghĩa là theo dõi cả events và trạng thái. Events cho bạn biết điều gì đã xảy ra. Trạng thái cho bạn biết bản ghi trông như thế nào ngay lúc này. Thời điểm rất quan trọng vì tích hợp hiếm khi chạy theo thứ tự hoàn hảo. Một event “updated” có thể đến trước “created,” hoặc đến hai lần, hoặc không bao giờ đến.
Mục tiêu là dữ liệu chính xác, chứ không chỉ dữ liệu mới. “Chính xác” có nghĩa là bạn không bỏ sót thay đổi, không áp dụng cùng một thay đổi hai lần, có thể phục hồi sau thời gian chết mà không cần dọn dẹp thủ công, và có thể chứng minh những gì bạn đã xử lý và khi nào.
Ví dụ thực tế: nhà cung cấp thanh toán gửi webhook như “payment_succeeded.” Ứng dụng của bạn tạo đơn hàng và đánh dấu là đã thanh toán. Nếu endpoint webhook của bạn tạm thời bị down, bạn có thể không bao giờ thấy event đó. Một job polling hỏi các khoản thanh toán được cập nhật “kể từ hôm qua” có thể hòa giải lỗ hổng và sửa trạng thái đơn hàng.
Hầu hết tích hợp thực tế dùng cả hai: webhooks để nhanh, polling để bổ sung và xác minh. Phương pháp quan trọng ít hơn các biện pháp an toàn xung quanh nó.
Độ trễ và tính tươi mới: cập nhật đến nhanh đến đâu
Khi mọi người so sánh webhooks và polling, họ thường muốn biết một điều: ứng dụng của bạn nhận biết thay đổi ở nơi khác nhanh đến mức nào. Độ tươi mới không chỉ là tính năng phụ — nó ảnh hưởng đến tickets hỗ trợ, công việc trùng lặp, và mức độ tin cậy của người dùng.
Polling có độ trễ tích hợp vì bạn chỉ hỏi theo lịch. Nếu bạn poll mỗi 5 phút, một cập nhật có thể đến muộn từ vài giây đến gần 5 phút, cộng thêm thời gian phản hồi API. Polling thường xuyên hơn cải thiện tính tươi mới, nhưng tăng lượng gọi API, chi phí, và khả năng chạm giới hạn tần suất.
Webhooks có thể cảm giác gần thời gian thực vì nhà cung cấp đẩy event ngay khi có thay đổi. Nhưng chúng không phải lúc nào cũng tức thì hay đảm bảo. Nhà cung cấp có thể gom nhiều event lại, thử lại sau, hoặc tạm dừng giao hàng. Hệ thống của bạn cũng thêm độ trễ (hàng đợi bị tắc, khóa cơ sở dữ liệu, deploy). Kỳ vọng chính xác hơn: nhanh khi mọi thứ ổn, cuối cùng đồng bộ khi không ổn.
Hình dạng lưu lượng (traffic shape) quan trọng. Polling tạo tải đều và dễ dự đoán. Webhooks gây bùng phát: một giờ cao điểm có thể gửi hàng trăm event trong một phút, rồi không có gì trong thời gian dài. Nếu bạn chấp nhận webhooks, hãy giả định có đột biến và lập kế hoạch đưa event vào hàng đợi để xử lý ở tốc độ kiểm soát được.
Trước khi thiết kế, chọn một cửa sổ độ tươi mục tiêu:
- Giây: thông báo cho người dùng, chat, trạng thái thanh toán
- Phút: công cụ hỗ trợ, giao diện admin, báo cáo nhẹ
- Giờ: đối chiếu ban đêm, phân tích ưu tiên thấp
Nếu đội sales cần lead mới hiển thị trong 1 phút, webhooks giúp đạt mục tiêu. Một “safety poll” mỗi vài giờ vẫn có thể bắt các event bị bỏ lỡ và xác nhận trạng thái cuối cùng.
Các chế độ lỗi bạn sẽ thấy trong production
Hầu hết tích hợp thất bại theo những cách nhàm chán, dễ tái tạo. Điều bất ngờ không phải là có gì đó hỏng, mà là nó hỏng một cách lặng lẽ. Tốc độ thì dễ tranh luận. Độ tin cậy mới là nơi công việc thực sự.
Lỗi polling thường có vẻ như “chúng tôi không thấy cập nhật,” ngay cả khi code trông ổn. Timeouts có thể cắt ngang request. Phản hồi cục bộ có thể thiếu sót nếu bạn chỉ kiểm tra HTTP 200 và không xác thực body. Thay đổi phân trang cũng thường gặp: API thay đổi thứ tự sắp xếp, sửa quy tắc trang, hoặc chuyển từ số trang sang cursor, và bạn vô tình bỏ qua hoặc đọc lại mục. Lỗi kinh điển khác là lọc theo “updated_since” dùng đồng hồ nội bộ, rồi bỏ lỡ cập nhật khi đồng hồ lệch hoặc nhà cung cấp dùng trường timestamp khác.
Webhooks lỗi khác. Giao hàng thường là “ít nhất một lần” (at least once), nên nhà cung cấp sẽ thử lại khi có lỗi mạng và bạn sẽ thấy trùng lặp. Nếu endpoint của bạn down 10 phút, bạn có thể nhận một đợt event cũ sau đó. Lỗi xác thực chữ ký cũng phổ biến: secret bị thay, bạn xác thực sai payload thô, hoặc proxy sửa header, và bỗng các event hợp lệ bị coi là không hợp lệ.
Chế độ lỗi chung cho cả hai là trùng lặp và giao hàng sai thứ tự. Giả định rằng bạn sẽ nhận cùng một event nhiều lần, event đến muộn, event đến sai thứ tự, và payload thiếu trường bạn mong đợi.
Bạn hiếm khi nhận được “exactly once.” Thiết kế theo mô hình “at least once” và xử lý an toàn. Lưu idempotency key (ID event hoặc phiên bản đối tượng của nhà cung cấp), bỏ qua phụ lặp, và chỉ áp dụng cập nhật khi nó mới hơn thứ bạn đã lưu. Ghi log những gì nhận và bạn đã làm gì với nó, để có thể replay một cách tự tin thay vì đoán mò.
Giới hạn tần suất và chi phí: giữ sử dụng API trong tầm kiểm soát
Giới hạn tần suất là lúc webhooks và polling ngừng là câu hỏi lý thuyết và trở thành vấn đề ngân sách và độ tin cậy. Mỗi request thêm một chi phí về thời gian và tiền, và có thể làm tổn hại mối quan hệ với nhà cung cấp.
Polling tiêu hao hạn mức vì bạn trả tiền cho việc kiểm tra kể cả khi không có gì thay đổi. Tệ hơn khi giới hạn áp cho từng người dùng hoặc từng token: 1.000 khách hàng polling mỗi phút có thể trông như một cuộc tấn công, dù mỗi khách “hành xử tốt.” Polling cũng nhân bản cuộc gọi dễ dàng (gọi endpoint list, rồi fetch chi tiết từng mục), đó là cách bạn chạm trần bất ngờ.
Webhooks thường giảm gọi API, nhưng tạo áp lực bùng nổ. Nhà cung cấp có thể gửi hàng nghìn event cùng lúc sau một sự cố, import hàng loạt, hoặc ra mắt sản phẩm. Một số sẽ throttle bạn với 429, một số thử lại mạnh, và một số sẽ bỏ event nếu endpoint của bạn chậm. Phía bạn cần có backpressure: trả lời nhanh, đưa công việc vào hàng đợi, và xử lý ở tốc độ an toàn.
Để giảm các cuộc gọi mà không mất tính đúng đắn, tập trung vào vài mô hình hữu dụng:
- Đồng bộ tăng dần dùng timestamp “updated since” hoặc change tokens
- Lọc phía server (subscribe chỉ các loại event bạn cần)
- Gom nhóm đọc và ghi (fetch chi tiết theo lô, ghi theo lô)
- Cache dữ liệu tham chiếu ổn định (gói, danh sách trạng thái, hồ sơ người dùng)
- Tách "đường dẫn thời gian thực" khỏi "báo cáo" (fast path vs nightly jobs)
Lập kế hoạch cho đỉnh trước khi nó xảy ra. Giữ một chế độ backfill riêng chạy chậm hơn, tôn trọng hạn ngạch, và có thể tạm dừng/tiếp tục.
Chọn phương pháp phù hợp: hướng dẫn quyết định đơn giản
Quyết định webhooks hay polling thường rút về điều này: bạn cần tốc độ, hay bạn cần cách đơn giản và dự đoán được để lấy cập nhật ngay cả khi nhà cung cấp không đáng tin cậy?
Polling thường là mặc định tốt khi bên thứ ba không hỗ trợ webhooks, hoặc khi luồng công việc của bạn chấp nhận độ trễ. Nó cũng dễ lý giải nếu bạn chỉ cần đồng bộ hàng ngày hoặc hàng giờ và API có filter “updated since” rõ ràng.
Webhooks là mặc định tốt khi thời gian quan trọng: “đơn hàng mới,” “thanh toán thất bại,” “ticket được gán.” Chúng giảm các cuộc gọi API lãng phí và có thể kích hoạt công việc ngay lập tức.
Quy tắc thực tế: dùng cả hai khi độ chính xác quan trọng. Để webhooks cho tốc độ, và polling làm sạch những gì bạn bỏ lỡ. Ví dụ: xử lý webhooks nhanh, sau đó chạy poll theo lịch mỗi 15 phút (hoặc vài giờ) để hòa giải lỗ hổng do event bị bỏ hoặc outage tạm thời.
Hướng dẫn nhanh:
- Nếu vài phút độ trễ là chấp nhận được, bắt đầu với polling.
- Nếu cập nhật phải xuất hiện trong vài giây, bắt đầu với webhooks.
- Nếu nhà cung cấp không ổn định hoặc event quan trọng, lên kế hoạch dùng webhook + polling.
- Nếu API bị giới hạn mạnh, ưu tiên webhooks kèm polling nhẹ.
- Nếu khối lượng dữ liệu lớn, tránh poll đầy đủ thường xuyên.
Trước khi chốt, hỏi nhà cung cấp vài câu và lấy câu trả lời rõ ràng:
- Có những loại event nào, và chúng đầy đủ không (create, update, delete)?
- Họ có thử lại webhooks không, và trong bao lâu?
- Có thể replay event hay fetch lịch sử event theo khoảng thời gian không?
- Họ có ký chữ ký webhook để bạn xác thực tính xác thực không?
- Họ có hỗ trợ truy vấn “updated since” cho polling hiệu quả không?
Bước từng bước: thiết kế sync giữ đúng dữ liệu
Một sync “chính xác” không chỉ là “dữ liệu xuất hiện.” Nó nghĩa là các bản ghi đúng khớp, thay đổi mới nhất thắng, và bạn có thể chứng minh điều gì đã xảy ra khi có vấn đề.
Bắt đầu với kế hoạch bạn có thể test và giám sát:
- Xác định nguồn sự thật và quy tắc. Chọn hệ thống nào chịu trách nhiệm cho từng trường. Ví dụ, CRM chịu trách nhiệm tên khách hàng, nhưng công cụ billing của bạn chịu trách nhiệm trạng thái subscription. Quyết định “tươi đủ” nghĩa là gì (ví dụ “trong 5 phút”) và lỗi nào chấp nhận được.
- Chọn định danh ổn định. Lưu ID duy nhất của bên thứ ba cùng với ID nội bộ. Tránh dùng email hay tên làm khóa (chúng thay đổi). Nếu có, lưu phiên bản hoặc timestamp “updated at” để phát hiện dữ liệu mới hơn.
- Lên kế hoạch import ban đầu, rồi cập nhật tăng dần. Xử lý import đầu như một job riêng có checkpoint để có thể tiếp tục. Sau đó, chỉ xử lý thay đổi (events, truy vấn “since”, hoặc cả hai) và lưu cursor như “last successful sync time.”
- Xử lý xóa và gộp có chủ ý. Quyết định xóa sẽ xóa bản ghi, lưu kho, hay đánh dấu inactive. Với merge, chọn ID tồn tại và giữ audit trail.
- Đặt các tín hiệu giám sát. Theo dõi sync lag, cuộc gọi thất bại, và hàng đợi bị kẹt. Cảnh báo khi lag vượt ngưỡng, không chỉ khi cái gì đó crash.
Khi triển khai, giữ các lựa chọn hiển thị trong mô hình dữ liệu: external IDs, timestamps, status fields, và nơi lưu checkpoint sync. Cấu trúc đó giữ sync đúng khi đời thực lộn xộn.
Idempotency và thứ tự: lõi của tích hợp đáng tin cậy
Nếu bạn làm webhooks và polling đủ lâu, một quy tắc luôn lặp lại: bạn sẽ thấy trùng lặp, thử lại, và cập nhật sai thứ tự. Nếu sync của bạn không thể xử lý an toàn việc xử lý lại cùng một message, nó sẽ lệch dần theo thời gian.
Idempotency nghĩa là “cùng input, cùng kết quả,” ngay cả khi nó đến hai lần. Xem mọi event đến như “có thể lặp lại,” và thiết kế handler an toàn. Mẫu phổ biến: tính dedup key, kiểm tra đã xử lý chưa, rồi áp dụng thay đổi.
Dedup key có đánh đổi. Event ID là tốt nhất khi nhà cung cấp cung cấp. Nếu không, bạn có thể dùng phiên bản đối tượng (incrementing revision). Cửa sổ timestamp như “bỏ lặp trong 10 phút” dễ vỡ vì event đến muộn.
Thứ tự là nửa còn lại. Thứ tự toàn cục hiếm, nên nhắm tới thứ tự trên mỗi đối tượng. Áp dụng cập nhật cho một ticket, invoice, hoặc customer chỉ khi phiên bản mới hơn phiên bản bạn đã lưu. Nếu không có phiên bản, dùng last-write-wins với quy tắc rõ ràng (ví dụ updated_at mới hơn thắng), và chấp nhận rằng lệch đồng hồ vẫn có thể gây edge case.
Lưu đủ dữ liệu để phục hồi và replay không đoán mò:
- Một “last seen” per-object (version hoặc updated_at)
- Cursor hoặc checkpoint cho các job polling
- Bảng các event ID đã xử lý (với quy tắc giữ nếu nó lớn)
- Payload thô trong thời gian ngắn để có thể chạy lại sửa lỗi
Ví dụ: một webhook thanh toán từ Stripe đến hai lần, rồi một update “paid” đến trước event “created.” Nếu bạn lưu phiên bản trạng thái invoice và bỏ qua cập nhật cũ hơn, bạn sẽ đúng.
Mô hình thử lại và phát lại tránh lệch dữ liệu lặng lẽ
Phần lớn tích hợp thất bại lặng lẽ. Một webhook đến muộn, job polling chạm giới hạn, hoặc app của bạn timeout khi lưu. Nếu không có retry và replay, hệ thống từ từ lệch cho đến khi khách hàng phàn nàn.
Retry webhook: nhận nhanh, xử lý an toàn
Nhà cung cấp thường thử lại khi bạn không trả mã HTTP thành công đủ nhanh. Hãy coi request webhook như thông báo giao hàng, không phải nơi làm việc nặng.
Mẫu webhook thực tế:
- Trả về nhanh với 2xx sau kiểm tra cơ bản (chữ ký, schema, timestamp).
- Lưu event với ID duy nhất và đánh dấu pending.
- Xử lý bất đồng bộ bằng worker và theo dõi số lần thử.
- Với lỗi tạm thời, thử lại sau. Với lỗi vĩnh viễn, dừng và cảnh báo.
- Dùng 4xx cho dữ liệu xấu và 5xx chỉ cho sự cố server thật sự.
Điều này tránh sai lầm phổ biến: nghĩ “webhook đã nhận” nghĩa là “dữ liệu đã đồng bộ.”
Retry polling: lịch sự với API
Polling lỗi theo cách khác. Rủi ro là một đợt retry ồ ạt sau một outage ngắn, làm tệ thêm giới hạn tần suất. Dùng exponential backoff cộng jitter, và giữ cursor “since” để không quét lại mọi thứ.
Khi bạn không thể xử lý bây giờ, đặt vào dead-letter queue (hoặc bảng) với lý do. Điều đó cho bạn nơi an toàn để kiểm tra, sửa mapping, và chạy lại mà không đoán xem gì mất.
Replay là cách bạn chữa lành sau event bị bỏ. Chiến lược replay đơn giản:
- Chọn khoảng thời gian (ví dụ 24 giờ gần nhất) hoặc tập bản ghi bị ảnh hưởng.
- Lấy lại trạng thái hiện tại từ nhà cung cấp.
- Áp dụng lại cập nhật idempotently và sửa sai lệch.
- Ghi lại những gì thay đổi và tại sao.
Ví dụ: nhà cung cấp billing gửi “invoice.paid,” nhưng database của bạn bị khóa 30 giây. Bạn dead-letter event, rồi replay bằng cách fetch lại invoice và trạng thái thanh toán và cập nhật bản ghi cho khớp.
Sai lầm thường gặp và cách tránh
Phần lớn bug đồng bộ không phải là vấn đề kiến trúc lớn. Chúng là các giả định nhỏ biến thành lệch lặng lẽ, bản ghi trùng, hoặc cập nhật bị bỏ.
Một vài lỗi thường gặp:
- Polling quá thường mà không có filter tăng dần. Lưu cursor (updated_at, event ID, page token) và chỉ hỏi thay đổi kể từ lần chạy thành công cuối.
- Coi webhooks là giao hàng đảm bảo. Giữ job backfill kiểm tra lịch sử gần đây (ví dụ 24-72 giờ) và hòa giải những gì bỏ lỡ.
- Bỏ qua trùng lặp. Làm mỗi lần ghi idempotent. Lưu event ID của nhà cung cấp (hoặc external ID ổn định) và từ chối áp dụng cùng thay đổi hai lần.
- Chấp nhận webhook mà không xác thực. Xác thực token chữ ký hoặc phương pháp xác minh nhà cung cấp cung cấp.
- Mù về sức khỏe sync. Theo dõi lag, kích thước backlog, thời gian chạy thành công cuối, và tỷ lệ lỗi. Cảnh báo khi lag vượt ngưỡng.
Nhiều tranh luận “webhooks vs polling” bỏ qua điểm then chốt: độ tin cậy đến từ các biện pháp an toàn xung quanh mỗi phương pháp. Một webhook thanh toán có thể đến hai lần hoặc đến muộn. Nếu hệ thống của bạn tạo bản ghi trực tiếp từ webhook mà không có idempotency, bạn có thể gửi tin nhắn hoặc tính tiền khách hàng hai lần.
Checklist nhanh để tích hợp khỏe mạnh
Kiểm tra hàng ngày trông giống nhau dù bạn dùng webhooks, polling hay cả hai. Bạn muốn biết dữ liệu còn tươi, lỗi có đang tích tụ, và bạn có thể phục hồi sạch sẽ không.
Checklist nhanh chạy trong vài phút:
- Tươi mới: so sánh “event nhận gần nhất” hoặc “poll hoàn thành gần nhất” với lag mong đợi.
- Lỗi: tìm retry tăng dần, hoặc job không tiến. Ghép số lỗi với thời gian “thành công cuối.”
- Hạn ngạch: kiểm tra số API call đã dùng và còn lại. Nếu gần giới hạn, giảm tốc polling và gom request.
- Đúng đắn: kiểm tra tổng số chéo hệ thống (ví dụ “đơn hôm nay”) và lấy mẫu vài bản ghi gần đây.
- Sẵn sàng phục hồi: xác nhận bạn có thể xử lý lại khoảng thời gian gần mà không gây trùng hay bỏ sót.
Thói quen hữu ích là định kỳ replay một khoảng thời gian bận rộn đã biết trong môi trường kiểm soát và xác nhận kết quả khớp production.
Ví dụ: kết hợp webhooks và polling trong workflow thực tế
Hãy tưởng tượng một team SaaS nhỏ cần đồng bộ ba hệ thống: CRM (contacts và deals), Stripe payments (charges và refunds), và công cụ hỗ trợ (ticket status).
Họ dùng setup ưu tiên webhook cho mọi thứ cần phản ứng nhanh. Event CRM cập nhật hồ sơ khách hàng và kích hoạt tác vụ nội bộ. Webhook Stripe tạo invoice, mở tính năng sau khi thanh toán, và đánh dấu tài khoản quá hạn khi charge thất bại. Với công cụ hỗ trợ, họ dùng webhooks nếu có, nhưng cũng giữ polling theo lịch vì trạng thái ticket có thể thay đổi hàng loạt.
Họ coi polling như lưới an toàn, không phải động cơ chính. Mỗi đêm, job đối chiếu kéo 24 giờ thay đổi qua các hệ thống và so sánh với dữ liệu app.
Rồi một outage thực sự xảy ra: endpoint webhook của họ down 20 phút trong quá trình deploy.
- CRM và Stripe thử lại giao hàng trong một thời gian.
- Một số event đến muộn, một số đến sai thứ tự, và một vài có thể hết hạn.
- Job đối chiếu phát hiện lỗ hổng (ID event thiếu hoặc tổng không khớp) và backfill các thay đổi bị thiếu.
Họ log: ID event đến, timestamp nhà cung cấp, ID bản ghi nội bộ, và kết quả cuối (created, updated, ignored). Cảnh báo kích hoạt khi: webhooks thất bại lặp lại, spike retry, hoặc đối chiếu tìm thấy vượt ngưỡng số cập nhật thiếu.
Bước tiếp theo: triển khai, giám sát và lặp lại
Mặc định thực tế cho đa số team là đơn giản: dùng webhooks cho tính tức thì, và giữ job polling nhỏ để đối chiếu. Webhooks đưa thay đổi đến nhanh. Polling bắt những gì bạn bỏ lỡ do outage, subscription cấu hình sai, hoặc nhà cung cấp thỉnh thoảng bỏ event.
Làm cho sync đúng trước khi bạn làm cho nó nhanh. Xem mọi thay đổi đến như thứ bạn có thể áp dụng an toàn nhiều lần.
Ba hành động nên làm trước:
- Mô tả bản đồ event và trường của nhà cung cấp sang mô hình nội bộ, bao gồm ý nghĩa của “delete,” “refund,” hay “status change” với bạn.
- Thiết kế idempotency ngay từ đầu: lưu external event ID hoặc version, và làm mỗi cập nhật an toàn khi chạy lại.
- Thêm replay có chủ ý: giữ cursor “since last seen” hoặc poll theo cửa sổ thời gian, và dựng giao diện admin để chạy lại khoảng khi có vấn đề.
Khi chạy, giám sát giữ cho nó chạy. Theo dõi tần suất giao hàng webhook, lỗi phân loại theo lý do (timeouts, 4xx, 5xx), và khoảng cách job đối chiếu đang lùi bao xa. Cảnh báo khi “không có event nhận” cũng như khi “quá nhiều event nhận.”
Nếu bạn muốn xây mà không tự viết backend từ đầu, AppMaster (appmaster.io) là một lựa chọn không cần code cho phép bạn mô hình dữ liệu, tạo endpoint webhook, và thiết kế luồng retry/replay bằng công cụ trực quan, đồng thời vẫn sinh ra mã nguồn thực khi triển khai.


