Bài viết dưới đây xây dựng một bộ khung cho Webhook và Callback để nhận các sự kiện của Official Account đã liên kết với ứng dụng.
Webhook là gì?
Webhook được sử dụng để đón nhận các sự kiện tương tác giữa Zalo Official Account và người quan tâm. Các sự kiện đó liên quan đến việc người dùng quan tâm và bỏ quan tâm; nhận, gửi tin nhắn; thêm, bớt hoặc gắn nhãn (tag) vào người quan tâm.
Webhook có thể được sử dụng để:
- Tạo Chatbot: Tự động trả lời lại khi người dùng gửi một tin nhắn có cú pháp nào đó.
- Cập nhật thông tin liên quan đến người quan tâm lên CRM.
Webhook được gọi từ Zalo đến Server nên không thể hiển thị lên Client. Để client nhận được những sự kiện này có thể dùng một trong những phương pháp:
- Client liên tục Polling dữ liệu mới từ Server.
- Server gửi Push Message đến Client.
Các vấn đề khi xây dựng Webhook
1) Dữ liệu trả về qua Webhook có thể bị “inject” từ bên ngoài. Để xác thực thông tin trả về, ta phải so sánh giá trị trong header X-ZEvent-Signature
với mã hash SHA256
của chuỗi kết hợp 4 thông tin sau theo thứ tự:
- ID của ứng dụng.
- Chuỗi JSON trả về trong body.
- Timestamp được lấy từ chuỗi JSON trên.
- Mã bí mật của Official Account được liên kết. (Nhớ giữ bí mật)
Lưu ý: OA Secret Key là khóa bí mật khi liên kết với Official Account, không phải là khóa bí mật của ứng dụng (App Secret Key).
2) Khi dữ liệu trả về có trường kiểu Long
và giá trị quá lớn sẽ khiến json_decode
xử lý sai. Vì vậy ta phải yêu cầu chuyển các số lớn BigInt
thành string
bằng cách thêm option JSON_BIGINT_AS_STRING
.
3) Webhook luôn phải trả về HTTP Code 200 OK
và phải phản hồi trong vòng 2 giây. Nếu không Zalo sẽ coi Webhook là không hoạt động và sẽ liên tục gửi lại trong vòng 12 giờ trước khi vô hiệu hóa quyền sử dụng Webhook của ứng dụng.
4) Do giới hạn 2 giây của Webhook, nếu cần xử lý phức tạp thì hãy chuyển data cho một process khác rồi thoát script luôn. Một số technique tham khảo:
- Fire and Forget POST request. Xem thêm »
- IPC với một Daemon bằng Message Queue, UNIX Domain Socket.
- Remote Procedure Call với một service khác (ví dụ dùng gRPC).
5) Webhook không thể chủ động lấy được Access Token như từ phía Client được vì vậy ta phải tìm cách “chôm chỉa” access token & refresh token của một admin lúc người đó đăng nhập lần đầu và dùng nó ở phía Server.
Nếu là Admin của Official Account, ta có thể sử dụng API Explorer để khởi tạo access token & refresh token ban đầu rồi lưu vào đâu đó như database chẳng hạn. Mỗi khi hết hạn ta lại dùng refresh token để lấy access token & refresh token mới, rồi lại ghi đè lên giá trị cũ.
Mã nguồn bộ khung của Webhook
Dưới đây là mã nguồn khung của tất cả Webhook viết bằng ngôn ngữ PHP có hỗ trợ giải quyết vấn đề 1 → 3. Phần TODO là code xử lý theo ứng dụng của bạn.
webhooks.php
<?php define( 'OA_SECRET_KEY', "{oa-secret-key}" ); $json = file_get_contents("php://input"); $headers = getallheaders(); if ( 0 < strlen($json) && isset($headers["X-ZEvent-Signature"]) ) { $data = json_decode($json, false, 512, JSON_BIGINT_AS_STRING); if ( $data ) { // Calculate the MAC value from received data $mac_1 = "mac=" . hash( "sha256", $data->app_id . $json . $data->timestamp . OA_SECRET_KEY ); $mac_2 = $headers["X-ZEvent-Signature"]; if ( 0 === strcmp($mac1, $mac2) ) { // data verified // TODO: Process the received event data // or forward to another process. } } }