Bỏ qua tới nội dung

"Callback là gì? Phân biệt Callback, Webhook và Polling khi các hệ thống gọi nhau"

Callback là gì, webhook khác callback ra sao, polling dùng khi nào? Giải thích bản chất qua ví dụ tích hợp cổng thanh toán, có sơ đồ minh hoạ dễ hiểu.

19 phút đọc

Callback là gì? Phân biệt Callback, Webhook và Polling khi các hệ thống gọi nhau

Lần đầu tích hợp cổng thanh toán, bạn mở tài liệu và thấy dòng "cấu hình callback URL". Câu hỏi đầu tiên bật ra: callback ở đây có phải là cái hàm callback trong JavaScript không? Rồi tài liệu lại nhắc tới "IPN", có chỗ gọi là "webhook", có chỗ khuyên "nên gọi API truy vấn để kiểm tra lại".

Nhiều bạn đi qua đoạn này bằng cách copy code mẫu cho chạy được, rồi cập nhật trạng thái đơn hàng ngay trong trang mà khách được chuyển về sau khi thanh toán. Mọi thứ ổn cho tới ngày khách trả tiền xong rồi tắt tab, và đơn hàng nằm mãi ở trạng thái "chờ thanh toán".

Bài này giải thích tận gốc ba khái niệm callback, webhook và polling trong bối cảnh các hệ thống gọi qua lại với nhau, để bạn không chỉ biết chúng là gì mà còn hiểu vì sao chúng tồn tại.

Callback là gì?

Callback là thứ bạn đưa cho bên khác để họ gọi lại cho bạn khi có kết quả: trong code, đó là một hàm; giữa các hệ thống, đó là một URL.

Có ba ý cần nắm:

  • Bạn cung cấp trước, họ gọi sau. Bạn đưa URL (hoặc đăng ký nó) từ trước, và họ chỉ gọi về khi có chuyện cần báo.
  • Người quyết định lúc nào gọi là họ, không phải bạn. Bạn không biết và không cần biết khi nào kết quả có.
  • Phải có tương tác trước đó. Nếu một hệ thống khác chỉ đơn giản gọi vào API của bạn, chẳng hạn app đối tác gọi GET /api/products, thì đó là gọi API bình thường, không phải callback. Chữ "call back" nghĩa là gọi lại, nên trước đó phải có một lần bạn liên hệ họ.

Trong phạm vi bài này, mình nói về callback ở góc nhìn giữa các hệ thống. Callback trong code (truyền hàm vào hàm) dùng chung ý tưởng, nhưng sẽ được nói ở một bài khác.

Ví dụ đời thường: đưa xe đi sửa

Bạn mang xe ra tiệm sửa. Thợ bảo phải kiểm tra mới biết hỏng gì, chưa nói được bao lâu xong. Bạn có ba cách:

  1. Ngồi chờ ở tiệm cho tới khi sửa xong. Đơn giản, nhưng mất cả buổi và bạn không làm được việc gì khác.
  2. Về nhà, cứ 30 phút gọi điện hỏi "xe xong chưa anh?". Bạn được tự do hơn, nhưng gọi nhiều lần vô ích, và có khi xe xong từ lâu mà 25 phút sau bạn mới biết.
  3. Để lại số điện thoại: "Xong thì gọi em nhé." Bạn đi làm việc khác, khi xe xong thì tiệm gọi bạn ngay.

Ánh xạ sang kỹ thuật:

Đời thường Kỹ thuật
Ngồi chờ ở tiệm Gọi API đồng bộ, giữ kết nối chờ kết quả
Cứ 30 phút gọi điện hỏi Polling
Số điện thoại để lại Callback URL
Tiệm gọi điện báo xe xong Request callback (webhook) gửi về server của bạn

Ví dụ này sẽ đi theo bạn suốt bài, và cả các tập sau của series.

Bản chất: vì sao cần callback?

Vấn đề gốc: không biết khi nào kết quả có

Khi bạn tạo một giao dịch thanh toán, kết quả không nằm trong tay cổng thanh toán, mà nằm trong tay khách hàng. Khách có thể trả tiền sau 10 giây, sau 10 phút, hoặc không bao giờ trả. Hệ thống của bạn cần biết kết quả, nhưng không có cách nào biết trước khi nào.

Cách ngây thơ thứ nhất: giữ kết nối chờ

Gọi API cổng thanh toán và đợi trong cùng request đó cho tới khi có kết quả. Cách này không chạy được trong thực tế: kết nối HTTP sẽ timeout, mỗi request đang chờ chiếm một worker hoặc thread của server, và chỉ vài trăm khách cùng thanh toán là server nghẽn.

Cách ngây thơ thứ hai: hỏi liên tục (polling)

Cứ vài giây gọi API "giao dịch này xong chưa?". Cách này chạy được, nhưng phần lớn request là vô ích, cả hai bên tốn tài nguyên. Bạn cũng luôn biết kết quả trễ: hỏi 10 giây một lần thì có thể trễ tới gần 10 giây. Muốn nhanh hơn thì hỏi dày hơn, và càng tốn hơn.

Callback: đảo chiều chủ động

Mấu chốt của callback là đảo ngược ai là người chủ động. Bên đang giữ kết quả (cổng thanh toán) là bên duy nhất biết chính xác lúc nào có kết quả. Vậy thì để chính họ báo về là hợp lý nhất: không request thừa, không độ trễ, không kết nối nào phải treo.

Cái giá phải trả

Callback không miễn phí. Khi chọn nó, bạn nhận về mấy trách nhiệm mới:

  • Endpoint của bạn phải công khai trên internet. Ai cũng gọi vào được, kể cả kẻ giả mạo. Bạn phải xác thực chữ ký (thường là HMAC, một kiểu "con dấu" tạo từ dữ liệu và khoá bí mật chỉ hai bên biết).
  • Có thể bị gọi nhiều lần. Nếu server bạn phản hồi chậm hoặc lỗi, bên kia sẽ gọi lại (retry). Code của bạn phải idempotent, tức là xử lý một sự kiện nhiều lần vẫn cho kết quả như xử lý một lần.
  • Có thể bị lỡ. Server bạn sập đúng lúc, mạng lỗi, hết số lần retry. Bạn cần một phương án dự phòng.

Chính vì cái giá này mà polling vẫn còn đất sống, như bạn sẽ thấy ở phần cuối.

Hai kiểu callback trong tích hợp thanh toán

Đây là chỗ nhiều người nhầm nhất. Một luồng thanh toán thường có hai đường báo kết quả về hệ thống của bạn:

1. Redirect callback (Return URL). Sau khi thanh toán, cổng thanh toán chuyển hướng trình duyệt của khách về một URL của bạn, kèm kết quả trên query string. Với VNPay, đây là vnp_ReturnUrl, được gửi kèm mỗi lần tạo giao dịch. Mục đích của nó là đưa khách quay lại site của bạn và hiển thị trang "Thanh toán thành công".

2. Server-to-server callback (IPN / webhook). Server của cổng thanh toán gọi thẳng vào server của bạn, khách hàng không dính líu gì. VNPay gọi là IPN (Instant Payment Notification), URL này được khai báo trong trang quản trị merchant. Stripe hay GitHub gọi kiểu này là webhook.

Vậy webhook là gì? Webhook chính là callback kiểu server-to-server, gửi qua HTTP khi có sự kiện. Đây không phải hai khái niệm khác nhau: callback là ý tưởng, webhook là tên gọi phổ biến của một cách hiện thực ý tưởng đó giữa các hệ thống.

Quy tắc vàng: trang hiển thị dựa vào Return URL, còn cập nhật database dựa vào IPN.

Return URL đi qua trình duyệt, nên nó không đáng tin để cập nhật dữ liệu. Khách có thể tắt tab trước khi được redirect, mạng điện thoại có thể rớt, và người dùng có thể tự sửa tham số trên thanh địa chỉ.

Minh hoạ

Luồng thanh toán đầy đủ

sequenceDiagram
    participant K as Khách hàng
    participant S as Server của mình
    participant P as Cổng thanh toán
    S->>P: Tạo giao dịch (kèm Return URL)
    P-->>S: Link trang thanh toán
    S->>K: Chuyển hướng sang trang thanh toán
    K->>P: Thanh toán
    P->>S: IPN (server-to-server)
    Note over S: Xác thực chữ ký,<br/>cập nhật đơn hàng
    S-->>P: Phản hồi "đã nhận"
    P->>K: Redirect về Return URL
    K->>S: Mở trang kết quả

Hình 1: Hai đường kết quả trở về server của bạn. Để ý IPN đi thẳng từ cổng thanh toán sang server, còn Return URL phải đi vòng qua trình duyệt của khách. Đường nào có thể đứt giữa chừng?

Alt text: Sơ đồ tuần tự luồng thanh toán giữa khách hàng, server và cổng thanh toán, có hai đường báo kết quả là IPN và Return URL.

Polling và callback đặt cạnh nhau

sequenceDiagram
    title Polling: mình chủ động hỏi
    participant A as Hệ thống mình
    participant B as Hệ thống họ
    A->>B: Xong chưa?
    B-->>A: Chưa
    A->>B: Xong chưa?
    B-->>A: Chưa
    A->>B: Xong chưa?
    B-->>A: Xong rồi, kết quả đây
sequenceDiagram
    title Callback: họ chủ động báo
    participant A as Hệ thống mình
    participant B as Hệ thống họ
    A->>B: Xử lý giúp, xong thì gọi URL này
    B-->>A: Đã nhận yêu cầu
    Note over B: ...đang xử lý...
    B->>A: Xong rồi, kết quả đây

Hình 2: Đếm số mũi tên đi ra từ "Hệ thống mình". Polling tốn nhiều lời gọi, phần lớn vô ích. Callback chỉ cần chờ một lần được gọi lại.

Alt text: Hai sơ đồ tuần tự so sánh polling (hỏi lặp lại nhiều lần) và callback (bên kia chủ động báo về một lần).

Xử lý callback an toàn

flowchart TD
    A[Nhận IPN] --> B{Chữ ký hợp lệ?}
    B -- Không --> X[Từ chối]
    B -- Có --> C{Đơn tồn tại,<br/>số tiền khớp?}
    C -- Không --> X
    C -- Có --> D{Đơn đã xử lý rồi?}
    D -- Rồi --> E[Phản hồi đã nhận, bỏ qua]
    D -- Chưa --> F[Cập nhật đơn hàng]
    F --> E

Hình 3: Bốn câu hỏi phải trả lời trước khi tin một callback. Nhánh "đã xử lý rồi" là thứ giúp bạn an toàn khi bị gọi trùng.

Alt text: Lưu đồ các bước kiểm tra khi nhận IPN: xác thực chữ ký, đối chiếu đơn hàng và số tiền, kiểm tra trùng lặp, rồi cập nhật đơn.

Callback, Webhook và Polling khác nhau thế nào?

Tiêu chí Gọi API đồng bộ Polling Callback qua redirect Callback server-to-server (webhook/IPN)
Ai chủ động? Mình Mình (lặp lại) Họ, qua trình duyệt Họ
Kết quả đến khi nào? Ngay trong response Ở lần hỏi kế tiếp Khi khách được redirect Ngay khi có sự kiện
Đi qua trình duyệt? Không Không Có Không
Đáng tin để cập nhật dữ liệu? Có Có Không Có, nếu xác thực chữ ký
Dùng khi Xử lý nhanh, có kết quả ngay Không nhận được callback, cần đối soát Đưa người dùng quay lại site Nhận kết quả, sự kiện từ hệ thống khác

Câu hỏi để phân biệt nhanh:

  • Ai là người mở lời gọi mang kết quả? Nếu là mình, đó là gọi API hoặc polling. Nếu là họ, đó là callback.
  • Trước đó mình có đưa URL hay đăng ký gì với họ không? Có thì mới là callback, không thì chỉ là họ gọi API của mình.

Khi nào dùng, khi nào không

Dùng callback (webhook) làm kênh chính khi kết quả đến sau một khoảng thời gian không đoán trước được: thanh toán, xử lý video, ký hợp đồng điện tử, sự kiện push code lên GitHub.

Dùng polling khi:

  • Bên kia không hỗ trợ callback.
  • Server của bạn không thể nhận request từ internet (chạy trong mạng nội bộ, sau firewall).
  • Bạn cần đối soát dự phòng: callback có thể bị lỡ, nên định kỳ quét các đơn còn "chờ thanh toán" quá lâu và gọi API truy vấn trạng thái giao dịch để kiểm tra lại.

Cách làm phổ biến trong thực tế là kết hợp cả hai: callback làm chính, polling làm lưới an toàn. Giống như bạn để lại số điện thoại cho tiệm sửa xe, nhưng nếu tới chiều vẫn chưa thấy ai gọi, bạn sẽ tự gọi hỏi.

Những hiểu lầm thường gặp

"Hệ thống khác gọi vào API của mình thì gọi là callback." Chưa chắc. Callback đòi hỏi mình đã đưa URL hoặc đăng ký với họ từ trước, và họ gọi lại để báo kết quả của việc đó. Một đối tác gọi API lấy danh sách sản phẩm là gọi API thông thường.

"Webhook và callback là hai thứ khác nhau." Webhook là callback kiểu server-to-server qua HTTP. Nhiều tài liệu dùng hai từ thay cho nhau. Điều cần phân biệt thật sự là callback qua trình duyệt và callback server-to-server.

"Khách được redirect về trang thành công nghĩa là đã thanh toán." Return URL đi qua trình duyệt, có thể bị đứt giữa chừng hoặc bị sửa tham số. Chỉ IPN đã xác thực chữ ký mới là nguồn sự thật.

"Đã có callback thì không cần polling nữa." Callback có thể bị lỡ khi server bạn sập hoặc hết lượt retry. Không có cơ chế đối soát thì sớm muộn bạn sẽ có đơn hàng bị "treo".

"Mỗi sự kiện chỉ được gửi một lần." Hầu hết các bên gửi callback đều retry khi không nhận được phản hồi đúng, và tài liệu của các thư viện VNPay cũng lưu ý IPN có thể được gọi nhiều lần. Code xử lý phải idempotent.

Tóm tắt nhanh

  • Callback là URL bạn đưa cho hệ thống khác để họ gọi lại khi có kết quả: bạn đưa trước, họ gọi sau.
  • Webhook là callback server-to-server qua HTTP. IPN của VNPay là một ví dụ.
  • Polling là cách ngược lại: bạn chủ động hỏi định kỳ "xong chưa?".
  • Trang hiển thị dựa vào Return URL, cập nhật database dựa vào IPN/webhook.
  • Thực tế: callback làm chính, polling làm dự phòng; xử lý callback phải xác thực chữ ký và idempotent.

Tự kiểm tra

1. Khách thanh toán thành công nhưng tắt trình duyệt ngay lập tức, trước khi được redirect về site. Nếu hệ thống của bạn làm đúng như bài này, đơn hàng có được cập nhật không?

Đáp án

Có. Đơn hàng được cập nhật qua IPN, là lời gọi từ server cổng thanh toán sang server của bạn, không phụ thuộc vào trình duyệt. Khách chỉ mất trang hiển thị kết quả, còn dữ liệu vẫn đúng. Nếu bạn cập nhật đơn ở Return URL thì đơn này sẽ bị treo.

2. Cổng thanh toán gửi IPN 3 lần cho cùng một giao dịch thành công. Hệ thống của bạn có thể gặp chuyện gì, và cần làm gì?

Đáp án

Nếu không kiểm tra, bạn có thể xử lý đơn 3 lần: cộng điểm thưởng 3 lần, gửi 3 email, trừ kho 3 lần. Cần kiểm tra trạng thái đơn trước khi xử lý. Nếu đơn đã không còn ở trạng thái chờ thì bỏ qua, nhưng vẫn phản hồi "đã nhận" để bên kia ngừng gửi lại.

3. Server của bạn chạy trong mạng nội bộ công ty, không có địa chỉ nào trên internet gọi vào được. Bạn vẫn cần biết kết quả xử lý từ một dịch vụ bên ngoài. Dùng cách nào?

Đáp án

Polling. Callback đòi hỏi bên kia gọi được vào server của bạn. Khi điều đó không khả thi, bạn phải chủ động hỏi định kỳ. Đây là một trong những lý do polling vẫn tồn tại dù kém hiệu quả hơn.


Tập tiếp theo: Ở bài này, các server có thể gọi nhau qua URL công khai. Nhưng trình duyệt của người dùng thì không có URL nào để server gọi tới. Vậy làm sao ứng dụng chat hay hệ thống thông báo đẩy được tin nhắn xuống ngay lập tức? Đó là câu chuyện của Polling, Long polling, SSE và WebSocket, hẹn bạn ở tập 2. "Callback là gì? Phân biệt Callback, Webhook và Polling khi các hệ thống gọi nhau"

Bình luận