Truy vấn trạng thái là cách đơn giản nhất để lấy mã: gửi yêu cầu, xem phản hồi, nếu trống thì lặp lại sau vài giây. Sự đơn giản này đánh lừa người dùng: mỗi yêu cầu thừa đều tiêu tốn giới hạn API, còn độ trễ vẫn không cố định, vì mã có thể xuất hiện ngay sau lần kiểm tra và nằm chờ đến chu kỳ kế tiếp. Webhook hoạt động khác: máy chủ tự thông báo về sự kiện ngay khi nó xảy ra. Chúng ta sẽ xem việc chuyển sang webhook mang lại gì trên thực tế, cần chuẩn bị gì ở phía bạn, cách phân biệt lệnh gọi thật với hàng giả và khi nào vẫn không thể bỏ polling.

Push và truy vấn định kỳ: webhook mang lại gì

Với polling, client gọi endpoint trạng thái vài giây một lần, và cho đến khi nhận được mã, gần như mọi phản hồi đều có nghĩa là "chưa có gì mới". Khi có chục lượt kích hoạt chạy song song, đó đã là hàng trăm lệnh gọi thừa mỗi phút, chạm giới hạn API mà vẫn không loại bỏ được độ trễ giữa lúc mã xuất hiện trên máy chủ và lúc ứng dụng biết về nó. Bản thân mã là gì đã được giải thích trong bài mã OTP là gì.

Webhook đảo ngược mô hình này: máy chủ bên gửi tự gửi yêu cầu đến địa chỉ webhook ngay khi sự kiện sẵn sàng. Độ trễ rút xuống bằng thời gian chuyển một yêu cầu HTTP, còn số lệnh gọi API giảm còn một lần cho mỗi lượt kích hoạt thay vì hàng chục lần truy vấn trong lúc chờ kết quả.

Cần chuẩn bị gì ở phía bạn

Yêu cầu đầu tiên là một địa chỉ webhook truy cập được công khai. Bên nhận phải phản hồi từ bên ngoài qua HTTPS: bằng tên miền hoặc IP công khai, không dùng địa chỉ mạng nội bộ và không có proxy nằm giữa bên gửi và máy chủ. Nếu địa chỉ không phân giải được từ internet, sự kiện sẽ không có nơi để đến — bên gửi sẽ ghi nhận lỗi kết nối chứ không phải sự im lặng ở phía họ. Các nguyên nhân khiến bên nhận không truy cập được được phân tích trong bài về việc gọi ngược bị gián đoạn.

Yêu cầu thứ hai là tốc độ phản hồi. Trình xử lý webhook phải nhận phần thân yêu cầu và trả về mã 2xx ngay, không chờ logic nghiệp vụ — phân tích văn bản, tìm mã bằng regex, ghi vào cơ sở dữ liệu. Phần việc này được đưa vào hàng đợi và chạy trong một tiến trình riêng. Nếu xử lý đồng bộ trước khi phản hồi, chỉ cần cơ sở dữ liệu hoặc dịch vụ bên thứ ba chậm là sẽ gây timeout, và bên gửi coi như việc gửi thất bại, dù sự kiện thực tế đã đến nơi.

Chữ ký yêu cầu: cách kiểm tra webhook là thật

Địa chỉ webhook công khai có thể bị bất kỳ ai tìm ra hoặc nhìn thấy trong lưu lượng bị chặn bắt. Nếu không kiểm tra tính xác thực, endpoint sẽ chấp nhận mọi phần thân với nội dung tùy ý — chỉ cần gửi một yêu cầu POST với "mã" bịa ra là trình xử lý sẽ nuốt nó như một sự kiện thật.

Chữ ký bịt kín lỗ hổng này: bên gửi băm phần thân yêu cầu bằng khóa bí mật và đặt kết quả vào header, bên nhận tính lại giá trị băm bằng cùng khóa và so sánh. Giá trị khớp xác nhận cả nguồn gốc của webhook lẫn việc phần thân không bị thay đổi trên đường đi. Khóa chỉ được lưu trên máy chủ và không đưa vào mã phía client; khi nghi ngờ bị lộ, hãy thay khóa trong trang tài khoản và đồng thời cập nhật phần kiểm tra ở phía bạn.

Gửi lại webhook là điều bình thường, không phải sự cố

Bên gửi không thể hoàn toàn chắc chắn rằng mã 2xx nghĩa là "sự kiện đã được lưu", chứ không phải "máy chủ đã phản hồi nhưng quá trình xử lý đổ vỡ một giây sau đó". Vì vậy phần lớn hệ thống gửi lại webhook theo lịch: sau vài giây, rồi sau một phút, và cứ thế vài lần, cho đến khi nhận được xác nhận hoặc hết số lần thử. Kết quả là cùng một mã với cùng một mã định danh sự kiện có thể đến hai, thậm chí ba lần.

Trình xử lý phải có tính idempotent: trước khi ghi, hãy kiểm tra xem mã với định danh kích hoạt hoặc sự kiện này đã được lưu chưa, và khi gặp bản lặp thì chỉ cần trả về 200, không tạo lại gì cả. Việc bên gửi thử lại gần giống với cách bạn nên thử lại các yêu cầu của chính mình tới API của bên khác — các nguyên tắc chung được trình bày trong bài lỗi API: cách xử lý và thử lại. Nếu bên nhận không truy cập được trong suốt khoảng thời gian gửi lại, sự kiện chỉ không bị mất khi có một điều kiện — bên gửi có hàng đợi các sự kiện chưa gửi được, và bạn có thể lấy sự kiện bị bỏ lỡ bằng một yêu cầu trạng thái riêng.

Khi nào polling vẫn phù hợp

Nếu tác vụ không có địa chỉ công khai cố định — môi trường thử nghiệm không có tên miền, script chạy một lần trên máy phía sau NAT, đợt chạy thử ngắn sẽ tắt sau một ngày — thì thiết lập webhook cho nó là vô nghĩa. Polling với khoảng cách vài giây đáp ứng được tác vụ đơn lẻ hoặc hiếm gặp mà không cần hạ tầng bên nhận và chữ ký.

Trường hợp thứ hai là yêu cầu thực sự đơn lẻ: một mã cho một lượt kích hoạt, không có luồng sự kiện, khi lợi ích của webhook không bù được công sức thiết lập địa chỉ công khai và hàng đợi xử lý. Với việc nhận mã thường xuyên hoặc song song, tương quan đảo ngược: polling bắt đầu ngốn giới hạn API nhanh hơn mức nó tiết kiệm được thời gian tích hợp.

Câu hỏi thường gặp

Có thể dùng cùng một địa chỉ webhook cho nhiều dịch vụ không?

Về mặt kỹ thuật là được, nếu trình xử lý phân biệt các sự kiện theo trường định danh dịch vụ hoặc lượt kích hoạt trong phần thân yêu cầu. Thực tế hơn là tách các nguồn ra các đường dẫn riêng trên cùng một tên miền — như vậy dễ đọc log hơn và dễ cập nhật chữ ký cho một nguồn mà không đụng đến các nguồn còn lại.

Phải làm gì nếu nhà cung cấp không hỗ trợ gửi lại?

Hãy chạy song song việc polling trạng thái như một lớp bảo hiểm, phòng khi bên nhận không truy cập được vào thời điểm duy nhất push được thử. Hãy truy vấn thưa — vài phút một lần: như vậy là đủ để nhặt lại sự kiện bị bỏ lỡ mà không biến truy vấn thành kênh chính.

Làm sao kiểm tra chữ ký hoạt động đúng trước khi đưa lên môi trường thật?

Gửi một sự kiện thử với phần thân cố ý bị hỏng hoặc header chữ ký sai và đảm bảo trình xử lý từ chối nó trước khi ghi vào cơ sở dữ liệu. Sau đó lặp lại với chữ ký đúng và cùng một định danh sự kiện hai lần liên tiếp — lần gọi thứ hai phải trả về 200 mà không tạo lại gì.

Định dạng sự kiện, header chữ ký, cấu trúc phần thân yêu cầu và các tham số gửi lại để nhận mã qua webhook được mô tả trong tài liệu API.