Bỏ qua để đến nội dung

Webhooks

14 phút đọc

Ferry hỗ trợ gửi webhook tới địa chỉ tiếp nhận của đối tác khi phát sinh các sự kiện nghiệp vụ trong hệ thống. Cơ chế này được thiết kế theo nguyên tắc báo hiệu nhanh, yêu cầu đối tác xác nhận phản hồi tức thì và chủ động tra cứu ngược lại API để lấy dữ liệu chi tiết.

Ferry gửi sự kiện webhook ra ngoài tới địa chỉ mà tổ chức của bạn khai báo. Tuy nhiên, cơ chế gửi có ba đặc điểm cốt lõi bạn cần nắm rõ trước khi triển khai tích hợp:

  1. Không có chữ ký số: Không có HMAC, không có chuỗi bí mật chia sẻ, không có dấu thời gian và không có mã dùng một lần (nonce). Bạn không thể xác thực nguồn gốc yêu cầu chỉ dựa vào gói tin nhận được.
  2. Không có cơ chế thử lại: Nếu gửi thất bại hoặc kết nối lỗi, hệ thống chỉ ghi log phía máy chủ và bỏ qua. Sự kiện bị mất vĩnh viễn.
  3. Hạn chờ phản hồi cứng 2.000 mili-giây: Nếu địa chỉ tiếp nhận của bạn phản hồi chậm hơn 2 giây, kết nối bị ngắt và sự kiện không được gửi lại.

Ba đặc điểm này dẫn đến nguyên tắc kiến trúc nhất quán: dùng webhook làm tín hiệu, dùng polling làm chốt chặn. Webhook cho bạn biết có sự kiện vừa xảy ra, sau đó hệ thống của bạn gọi ngược lại API để lấy dữ liệu chính xác nhất.

Điều kiện cầnVì saoĐọc ở đâu
Khoá API có quyền quản trị cài đặt tổ chứcDùng để khai báo địa chỉ tiếp nhận qua APIXác thực
Địa chỉ nhận chạy HTTPS công khai, phản hồi dưới 2 giâyHạn chờ xử lý tối đa là 2.000 msMục “Cơ chế gửi” bên dưới
Hàng đợi tiếp nhận nội bộ phía bạnTrả mã 200 ngay, xử lý nghiệp vụ sauMục “Mẫu xử lý an toàn” bên dưới
Tác vụ đối chiếu dữ liệu định kỳBù đắp cho các sự kiện có thể bị rơi mấtMục “Mẫu xử lý an toàn” bên dưới
Danh sách sự kiện cần đăng kýChọn đúng sự kiện thực tế phát sinhMục “Danh sách sự kiện” bên dưới

Sơ đồ dưới đây cho thấy đường đi của một sự kiện từ khi phát sinh bên trong Ferry đến địa chỉ tiếp nhận của bạn:

flowchart TB
A["Nghiệp vụ phát sinh trong Ferry"] --> B["Hệ thống bắt sự kiện"]
B --> C["Tra cứu các tổ chức đăng ký sự kiện này"]
C --> D{"Tổ chức có khai báo địa chỉ nhận không"}
D -->|Không| Z["Dừng, không gửi gì"]
D -->|Có| E["Duyệt qua từng địa chỉ tiếp nhận"]
E --> F{"Địa chỉ có điều kiện lọc không"}
F -->|Không có| H["Gửi sự kiện"]
F -->|Có| G{"Đánh giá điều kiện trên dữ liệu"}
G -->|Không khớp| Y["Bỏ qua địa chỉ này"]
G -->|Khớp| H
G -->|Điều kiện sai cú pháp| X["Lỗi ngắt quãng luồng xử lý<br/>có thể làm mất sự kiện"]
H --> I["Gửi dữ liệu tới địa chỉ của bạn<br/>hạn chờ 2000 ms<br/>tiêu đề x-event-type<br/>không chữ ký số"]
I --> J{"Địa chỉ phản hồi trong 2 giây"}
J -->|Có| K["Hoàn tất gửi webhook"]
J -->|Không hoặc lỗi| L["Ghi log phía Ferry<br/>không thử lại<br/>sự kiện mất vĩnh viễn"]

Sơ đồ dưới đây cho thấy mẫu xử lý sự kiện an toàn phía đối tác:

sequenceDiagram
autonumber
participant P as "Hệ thống của bạn"
participant Q as "Hàng đợi nội bộ"
participant F as "Ferry API"
F--)P: "Sự kiện webhook tới kèm tiêu đề loại sự kiện"
P->>Q: "Ghi nhận vào hàng đợi, chưa xử lý nghiệp vụ"
P-->>F: "Trả lời HTTP 200 trong vòng dưới 2 giây"
Q->>P: "Tiến trình nội bộ lấy dữ liệu ra xử lý"
P->>P: "Khử trùng lặp theo mã tài nguyên"
P->>F: "Gọi API tra cứu tài nguyên để lấy sự thật"
F-->>P: "Dữ liệu trạng thái thực tế"
P->>P: "Cập nhật hệ thống theo dữ liệu từ API"
alt Không nhận được sự kiện nào trong khoảng thời gian dự kiến
P->>P: "Tác vụ đối chiếu định kỳ kích hoạt"
P->>F: "Tra cứu danh sách tài nguyên thay đổi gần đây"
F-->>P: "Danh sách thực tế"
P->>P: "Bù đắp dữ liệu cho các sự kiện bị rơi mất"
end
BướcHệ thống của bạn làm gìFerry làm gìDấu hiệu thành côngLỗi thường gặp
1Dựng địa chỉ tiếp nhận HTTPS công khai với đường dẫn khó đoán—Địa chỉ trả về 200 khi gọi thửDùng đường dẫn mặc định dễ đoán như /webhook
2Phản hồi HTTP 200 ngay lập tức, đưa dữ liệu vào hàng đợi—Thời gian phản hồi luôn dưới 2 giây kể cả lúc tải caoXử lý nghiệp vụ đồng bộ khiến quá hạn 2 giây
3Cấu hình địa chỉ nhận và sự kiện trong cài đặt tổ chứcLưu cấu hình theo kênh tích hợpKiểm tra lại cài đặt thấy địa chỉ vừa lưuSợ mất cấu hình khác nên không dám cập nhật
4Thực hiện thao tác thật để kiểm chứng luồng sự kiệnPhát sự kiện tương ứngĐịa chỉ tiếp nhận nhận được gói tinTin rằng đăng ký là sẽ nhận mà không kiểm thử
5Khử trùng theo mã tài nguyên và gọi lại API lấy dữ liệu thậtTrả dữ liệu chuẩn của tài nguyênDữ liệu lưu trữ khớp với kết quả APIGhi thẳng dữ liệu từ webhook vào cơ sở dữ liệu
6Kích hoạt tác vụ đối chiếu định kỳ—Tác vụ phát hiện được dữ liệu lệch khi bỏ qua sự kiệnBỏ qua bước đối chiếu định kỳ
7Giám sát khoảng lặng của luồng sự kiện—Cảnh báo kích hoạt khi không có sự kiện quá lâuCoi việc im lặng kéo dài là hệ thống ổn định

Webhook không có vòng đời trạng thái riêng. Mỗi sự kiện chỉ có hai kết quả: đến nơi hoặc bị rơi mất. Hệ thống không lưu trữ hàng đợi gửi lại và không có giao diện lịch sử gửi webhook để tra cứu.

Trạng thái cần theo dõi nằm ở chính tài nguyên được thông báo (đơn hàng, booking, yêu cầu huỷ vé). Đối tác lấy trạng thái thực tế bằng cách gọi lại API tương ứng.

Địa chỉ nhận webhook được cấu hình trong phần tích hợp của cài đặt tổ chức. Đối tác có hai cách thiết lập:

  • Giao diện Dashboard: Truy cập trang Cài đặt thông báo và tích hợp sự kiện để quản trị URL và chọn danh sách sự kiện cần nhận.
  • API cài đặt tổ chức: Cập nhật trực tiếp bằng khoá API của tổ chức.

Mỗi cấu hình webhook bao gồm ba thông tin:

  • Địa chỉ nhận (URL): Địa chỉ HTTPS hợp lệ tiếp nhận phương thức POST.
  • Danh sách sự kiện đăng ký: Danh sách các mã sự kiện tổ chức muốn lắng nghe.
  • Điều kiện lọc (tuỳ chọn): Biểu thức lọc dữ liệu trước khi gửi.

Thao tác cập nhật cài đặt được gộp theo từng kênh cấu hình, không ghi đè hay làm mất các thiết lập khác của tổ chức.

Cơ chế gửi — mục quan trọng nhất của trang

Phần tiêu đề “Cơ chế gửi — mục quan trọng nhất của trang”
Hạng mụcThực tế vận hànhBạn phải làm gì
Phương thức HTTPLuôn gửi POST tới URL khai báoĐiểm tiếp nhận phải nhận phương thức POST
Nhãn sự kiệnNằm trong tiêu đề HTTP x-event-typeĐọc giá trị tiêu đề này để điều hướng xử lý nội bộ
Chữ ký sốKhông có. Không HMAC, không secret, không timestampCoi webhook là tín hiệu, gọi ngược API để lấy dữ liệu thật. Dùng URL khó đoán và lọc IP nếu khả thi
Hạn chờ2.000 mili-giây cứngNhận gói tin, đẩy vào hàng đợi, phản hồi 200 ngay
Thử lạiKhông có. Gặp lỗi chỉ ghi log máy chủ rồi bỏ quaBắt buộc chạy tác vụ đối chiếu định kỳ
Thứ tự sự kiệnKhông đảm bảo. Các sự kiện gửi độc lậpKhông xây dựng máy trạng thái dựa trên thứ tự nhận sự kiện
Hàng đợi phía FerryKhông có hàng đợi lưu trữ để gửi lạiSự kiện không tới nơi nghĩa là đã mất hẳn

Danh sách mã sự kiện trong hệ thống bao gồm:

  • Tổ chức: organization.created, organization.updated, organization.disabled, organization.enabled, child-organization.created
  • Booking: booking.created, booking.updated, booking.issuedTicket, booking.issuedTicketError, booking.cancelOrder, booking.hold_payment, booking.canceled_payment
  • Đơn hàng: order.created, order.updated, order.issuedTicket, order.childOrgIssuedTicket, order.childOrgIssuedTicketError, order.newManualIssueOrder, order.canceled, order.cancelTicket, order.forcedCanceled
  • Yêu cầu huỷ vé: cancel_ticket_request.created, .child_org_created, .updated, .accepted, .rejected, .accepted_refunded, .refunded, .recalled, .release_ticket
  • Khách hàng: customer.created, customer.updated, customer.deleted
  • Công ty: company.created, company.updated, company.deleted
  • Khách hàng tiềm năng (Lead): lead.created, lead.updated, lead.deleted
  • Người dùng: user.created, user.updated, user.deleted, user.created-with-profile
  • Khoá API: apiKey.created, apiKey.updated, apiKey.deleted
  • Chuyến: voyage.created, voyage.created.manually, voyage.updated, voyage.disabled, voyage.has-order-voyage-disabled
  • Theo dõi vé: ticketWatchlist.targetReached
  • Hoá đơn: invoice.created, invoice.updated, invoice.published, invoice.failed, invoice.canceled, invoice.orphanReconcileAlert

Khai được không có nghĩa là sẽ phát sự kiện

Phần tiêu đề “Khai được không có nghĩa là sẽ phát sự kiện”

Danh sách sự kiện cho phép đăng ký rộng hơn số lượng sự kiện thực tế được kích hoạt trong hệ thống. Một số mã sự kiện tuy có trong danh sách nhưng không có bộ lắng nghe nào phát ra webhook.

Quy trình kiểm chứng bắt buộc: sau khi đăng ký sự kiện, hãy thực hiện một thao tác thử nghiệm thực tế để xác nhận địa chỉ của bạn đã nhận được webhook tương ứng trước khi đưa vào nghiệp vụ chính thức.

Trong nhóm tổ chức, chỉ child-organization.created thực sự phát webhook. Bốn mã organization.created, organization.updated, organization.disabled và organization.enabled đăng ký được nhưng không phát; đừng dùng chúng làm nguồn cập nhật duy nhất.

Đối tượng nhận sự kiện theo cấu trúc tổ chức

Phần tiêu đề “Đối tượng nhận sự kiện theo cấu trúc tổ chức”
Loại sự kiệnTổ chức nhận được
Sự kiện của bookingTổ chức sở hữu booking
Sự kiện xuất vé của đơn hàngTổ chức tạo đơn, kèm theo một sự kiện riêng cho từng tổ chức cấp trên trong chuỗi (với dữ liệu giá NET tính theo góc nhìn cấp đó)
Sự kiện tạo tổ chức conTổ chức cha trực tiếp, không phải tổ chức mới tạo

Đối tác đa cấp lưu ý: bạn sẽ nhận hai loại sự kiện xuất vé khác nhau — một loại cho đơn của chính mình, một loại cho đơn của tổ chức con. Hai loại này mang nhãn và giá trị khác nhau, cần xử lý ở hai nhánh riêng biệt.

Dữ liệu trong webhook khác dữ liệu API trả về

Phần tiêu đề “Dữ liệu trong webhook khác dữ liệu API trả về”

Nội dung gói tin webhook không đi qua tầng lọc dữ liệu theo vai trò như các phản hồi API thông thường. Do đó, webhook có thể chứa một số trường dữ liệu mà lệnh gọi API không trả về.

  • Bảo mật: Coi nội dung webhook là dữ liệu nhạy cảm, không ghi log nguyên văn hoặc chuyển tiếp tùy tiện.
  • Kỹ thuật: Không xây dựng nghiệp vụ cốt lõi phụ thuộc vào các trường chỉ xuất hiện trong webhook mà không có trong hợp đồng API.

Bộ lọc điều kiện cho phép chỉ nhận các sự kiện khớp với tiêu chí mong muốn. Một số quy tắc quan trọng:

  • Hỗ trợ 6 toán tử so sánh cơ bản: bằng, khác, lớn hơn, lớn hơn hoặc bằng, nhỏ hơn, nhỏ hơn hoặc bằng.
  • So sánh kiểu dữ liệu nghiêm ngặt: chuỗi '1' không khớp với số 1.
  • Truy cập trường lồng nhau qua dấu chấm (order.code).
  • Kết hợp điều kiện bằng and, or, not (trong đó not chỉ áp dụng cho nhánh con đầu tiên).
  • Không khai báo điều kiện lọc nghĩa là luôn nhận toàn bộ sự kiện của loại đó.

Bộ lọc webhook khác bộ lọc động của API: webhook chỉ hỗ trợ 6 toán tử so sánh, còn bộ lọc API có 13. Không sao chép nguyên điều kiện giữa hai hệ.

Ở luồng huỷ vé, điều kiện sai cú pháp có thể làm hỏng cả vòng gửi webhook của tổ chức, không chỉ bỏ qua sự kiện không khớp. Nếu không thật sự cần lọc phía Ferry, hãy nhận toàn bộ rồi lọc tại hệ thống của bạn.

  1. Phản hồi ngay dưới 2 giây: Nhận dữ liệu, đẩy vào hàng đợi nội bộ, trả về HTTP 200. Mọi bước xử lý nghiệp vụ thực hiện bất đồng bộ sau đó.
  2. Khử trùng lặp theo mã tài nguyên: Dùng mã booking, mã đơn hoặc mã yêu cầu huỷ kết hợp loại sự kiện làm khoá chống trùng lặp.
  3. Coi webhook là tín hiệu: Lấy mã định danh từ webhook rồi gọi lại API để lấy dữ liệu thực tế.
  4. Chạy đối chiếu định kỳ: Quét danh sách tài nguyên thay đổi định kỳ để bù đắp các sự kiện bị rơi mất.
  5. Giám sát khoảng lặng: Thiết lập cảnh báo khi không nhận được sự kiện nào trong một khoảng thời gian bất thường.
Triệu chứngNguyên nhânCách xử lýCó nên gọi lại không
Không nhận được sự kiện sau khi đăng kýSự kiện không có bộ phát trong hệ thốngKiểm chứng bằng thao tác thật; chọn sự kiện thay thếKhông áp dụng
Nhận được lúc đầu rồi mất hẳn sự kiệnĐiều kiện lọc sai cú pháp gây lỗi ngắt quãngXoá hoặc sửa điều kiện lọc, thử lại bằng thao tác thậtKhông áp dụng
Dữ liệu sự kiện cũ hơn thực tếThứ tự gửi không đảm bảoLuôn gọi lại API để lấy dữ liệu mới nhấtKhông áp dụng
Mất sự kiện khi hệ thống của bạn tải caoPhản hồi chậm quá 2 giây nên Ferry ngắt kết nốiĐổi sang mô hình nhận rồi đẩy hàng đợi; bù bằng đối chiếuKhông — sự kiện đã mất không gửi lại
Nhận hai lần cùng một sự kiệnWebhook không mang khóa chống trùng để đối chiếuKhử trùng lặp theo mã tài nguyên và loại sự kiệnKhông áp dụng
Nhận sự kiện xuất vé cho đơn không phải của mìnhSự kiện dành cho tổ chức cha trong chuỗiTách luồng xử lý riêng cho sự kiện của đơn tổ chức conKhông áp dụng
Cập nhật cài đặt lo bị mất cấu hình khácCập nhật gộp theo kênhĐọc lại cài đặt để tự kiểm traKhông áp dụng

Trang này liên quan đến nhóm Organizations trên giao diện API tại https://api.ferry.vn/docs — nơi cung cấp các lệnh đọc và cập nhật cài đặt tổ chức:

Mục tiêu tra cứuNhóm trên giao diện API
Lệnh cập nhật cài đặt tổ chức (khai báo URL webhook và sự kiện)Organizations
Lệnh đọc cài đặt hiện tại của tổ chức để xác nhậnOrganizations
Cấu trúc dữ liệu sự kiện nhóm bookingBookings
Cấu trúc dữ liệu sự kiện nhóm đơn hàngOrders
Cấu trúc dữ liệu sự kiện nhóm huỷ véCancel Ticket Requests
Cấu trúc dữ liệu sự kiện nhóm khách hàngCustomers

Giao diện tài liệu API mô tả schema của từng tài nguyên tương ứng với nội dung sự kiện phát ra. Các thông tin về thời hạn phản hồi, chữ ký số hay cơ chế thử lại chỉ được đặc tả tại trang tài liệu này.

TrangĐọc khi nào
Cài đặt thông báoCần cấu hình webhook trực quan trên giao diện Dashboard
Đặt vé: vòng đời một bookingCần hiểu vì sao tra cứu trạng thái booking là nguồn dữ liệu chuẩn
Xuất vé và đơn hàngCần phân biệt hai loại sự kiện xuất vé cho tổ chức cha và tổ chức con
Huỷ vé và hoàn tiềnCần danh sách sự kiện cần thiết cho luồng huỷ vé
Tiền: số dư, hạn mức và công nợCần đối chiếu sự kiện thanh toán với sổ giao dịch
Quy ước chungCần nắm quy ước chung về dữ liệu và xử lý trùng lặp
Câu hỏiTình trạng
Kế hoạch bổ sung chữ ký số cho webhookHiện chưa có kế hoạch hỗ trợ; đối tác cần dùng đường dẫn bí mật và gọi lại API để xác thực.
Kế hoạch bổ sung cơ chế thử lại khi gửi lỗiHiện chưa hỗ trợ; đối tác bắt buộc duy trì tác vụ đối chiếu định kỳ.
Danh sách dải IP máy chủ gửi webhookChưa công bố danh sách IP chính thức; cần liên hệ Ferry nếu cần cấu hình tường lửa.
Sự kiện hoá đơn phát ra nhưng chưa có API đọc hoá đơn cho đối tácĐang chờ chính sách mở lại từ phía sản phẩm; hiện đối tác nhận được tín hiệu nhưng chưa có API đọc dữ liệu chi tiết.