Hướng dẫn

Gọi Zalo API bằng C#/.NET: OAuth, webhook và gửi tin OA

Hướng dẫn kỹ thuật tích hợp Zalo OA API bằng C#/.NET: OAuth v4 với PKCE, vòng đời token, typed HttpClient có retry, webhook ASP.NET Core kiểm chữ ký, idempotency và ghi log an toàn.

Tóm tắt: gọi Zalo OA API từ .NET cần những gì?

Để gọi Zalo OA API từ .NET, ứng dụng của bạn cần được OA cấp quyền qua OAuth v4 (có PKCE) để lấy access token và refresh token, sau đó gọi OpenAPI với header access_token. Theo tài liệu Zalo, access token có hiệu lực 25 giờ, refresh token 3 tháng và mỗi refresh token chỉ dùng được một lần. Phía .NET, hãy dùng typed HttpClient qua IHttpClientFactory, một dịch vụ quản lý token có khóa đồng bộ, pipeline retry của Microsoft.Extensions.Http.Resilience và một endpoint webhook kiểm chữ ký trước khi xử lý.

Bài này dành cho lập trình viên. Nếu cần góc nhìn nghiệp vụ (chọn loại tin, nối CRM, quy trình CSKH), xem hướng dẫn tích hợp Zalo OA. Mọi endpoint và giới hạn dưới đây lấy từ Zalo Platform Document Hub tại thời điểm viết; Zalo thay đổi API khá thường xuyên, nên hãy đối chiếu tài liệu hiện hành trước khi triển khai.

OAuth v4 với PKCE: lấy access token lần đầu

Tài liệu "Xác thực và ủy quyền cho Ứng dụng" mô tả luồng OAuth v4 gồm năm bước. Authorization code chỉ dùng một lần và có hiệu lực 10 phút kể từ lúc cấp.

  • Bước 1 – Tạo code verifier (chuỗi 43 ký tự gồm chữ hoa, chữ thường và số, khác nhau cho mỗi request) và lưu phía server. Code challenge là SHA-256 của code verifier, mã hóa Base64 không padding. Trang tổng quan Open APIs ghi Base64URL; trong .NET có thể dùng SHA256.HashData rồi Base64Url.EncodeToString (namespace System.Buffers.Text, .NET 9 trở lên).
  • Bước 2 – Trên Zalo for Developers, thiết lập callback URL, code challenge và chọn nhóm quyền cần thiết (ví dụ quyền gửi tin và thông báo qua OA, quyền nhận sự kiện quản lý tin nhắn).
  • Bước 3 – Gửi đường dẫn cấp quyền cho admin OA. Sau khi admin cho phép, callback URL nhận HTTP GET kèm code và oa_id. Dùng tham số state để biết code ứng với code verifier nào.
  • Bước 4 – POST tới https://oauth.zaloapp.com/v4/oa/access_token, Content-Type application/x-www-form-urlencoded, header secret_key, body gồm code, app_id, grant_type=authorization_code và code_verifier.
  • Bước 5 – Lưu access_token, refresh_token và expires_in (tính bằng giây; ví dụ trong tài liệu là 90000, tức 25 giờ).

Vòng đời token và cách refresh an toàn trong .NET

Theo tài liệu Zalo: mỗi access token đi kèm một refresh token; refresh token chỉ dùng một lần; sau khi refresh thành công, refresh token cũ bị vô hiệu và access token cũ hết hiệu lực ngay khi access token mới được tạo. Refresh dùng cùng endpoint với grant_type=refresh_token. Nếu refresh token hết hạn, admin OA phải cấp quyền lại từ đầu.

Hệ quả thiết kế quan trọng nhất: nếu hai tiến trình cùng refresh, một bên sẽ cầm refresh token đã mất hiệu lực. Vì vậy chỉ một nơi được phép refresh.

  • Tạo một ZaloTokenService dạng singleton, giữ token trong bộ nhớ và dùng SemaphoreSlim để chỉ một luồng refresh tại một thời điểm.
  • Lưu cặp token mới vào kho bền vững (cơ sở dữ liệu mã hóa hoặc secret store) ngay sau khi nhận, trước khi dùng access token mới. Nếu ghi thất bại, bạn có thể mất refresh token duy nhất còn hiệu lực.
  • Refresh chủ động trước khi hết hạn (ví dụ khi còn dưới vài giờ) bằng BackgroundService, thay vì đợi API trả lỗi token.
  • Khi chạy nhiều instance, dùng khóa phân tán (ví dụ lease trong cơ sở dữ liệu) để chỉ một instance refresh; các instance khác đọc token từ kho chung.
  • Không đưa secret_key, access token hay refresh token vào mã nguồn, log hoặc thông báo lỗi; nạp từ cấu hình bí mật qua IOptions.

Typed HttpClient, retry và gửi tin Tư vấn

Đăng ký một typed client, ví dụ services.AddHttpClient<ZaloOaClient>() với BaseAddress là https://openapi.zalo.me/, rồi gắn .AddStandardResilienceHandler(). Theo Microsoft Learn, handler chuẩn xếp chồng nhiều chiến lược resilience với giá trị mặc định để xử lý lỗi tạm thời; bạn có thể tùy biến qua AddResilienceHandler nếu cần pipeline riêng. Một DelegatingHandler riêng sẽ lấy access token từ ZaloTokenService và gắn header access_token cho từng request.

Tin Tư vấn dạng văn bản và đính kèm dùng POST https://openapi.zalo.me/v3.0/oa/message/cs, body JSON có recipient.user_id và message. Theo trang "Điều kiện gửi tin Tư vấn", app phải bật quyền gửi tin và thông báo qua OA, và người nhận phải có tương tác với OA trong vòng 7 ngày. Response trả về message_id, user_id, sent_time và thông tin quota; tài liệu ghi từ ngày 1/1/2026 tin Tư vấn trong khung 48 giờ được gửi miễn phí không giới hạn, và một số thuộc tính quota sẽ ngừng trả về sau ngày 1/3/2026, nên đừng để code phụ thuộc cứng vào các trường đó.

Thông báo chủ động (xác nhận đơn, nhắc lịch) đi qua ZBS Template Message: POST https://openapi.zalo.me/v3.0/oa/message/template với user_id, template_id và template_data, dùng mẫu đã được duyệt. Gửi qua số điện thoại và điều kiện tài khoản ZBS là phần bạn nên kiểm tra tài liệu hiện hành của Zalo.

Retry thế nào cho đúng với API gửi tin

  • Retry an toàn với lỗi mạng và HTTP 5xx khi request chưa chắc đã tới Zalo; nhưng một lần gửi tin có thể đã thành công dù client nhận timeout, nên retry gửi tin có thể tạo tin trùng.
  • Lưu trạng thái gửi theo khóa nghiệp vụ (ví dụ order_id + loại tin) trước khi gọi API; chỉ retry khi chưa có message_id cho khóa đó.
  • Zalo thường trả HTTP 200 kèm trường error khác 0 khi lỗi nghiệp vụ; đọc trường error trong body thay vì chỉ dựa vào status code. Bảng mã lỗi chi tiết, hãy kiểm tra tài liệu hiện hành của Zalo.
  • Lỗi token (access token không hợp lệ) nên kích hoạt một lần refresh rồi thử lại một lần, không lặp vô hạn.

Webhook trong ASP.NET Core minimal API: kiểm chữ ký trước khi xử lý

Zalo POST sự kiện (người dùng gửi tin, quan tâm OA, bấm nút) về webhook URL HTTPS bạn khai trong app. Trên diễn đàn Zalo for Developers, đội ngũ Zalo hướng dẫn kiểm header X-ZEvent-Signature theo công thức mac = sha256(appId + data + timeStamp + OAsecretKey), với data là nội dung sự kiện nhận được. Thứ tự ghép chuỗi, định dạng header và nguồn timestamp, hãy kiểm tra tài liệu hiện hành của Zalo và thử với sự kiện thật trước khi bật chế độ từ chối.

  • Đọc raw body đúng như byte nhận được (bật Request.EnableBuffering hoặc đọc stream vào chuỗi) trước khi parse JSON; băm lại JSON đã serialize sẽ không khớp chữ ký.
  • Tính SHA256.HashData(Encoding.UTF8.GetBytes(appId + rawBody + timestamp + oaSecretKey)), đổi sang hex chữ thường (giá trị header thường có tiền tố "mac=", hãy tách ra trước) và so sánh bằng CryptographicOperations.FixedTimeEquals để tránh rò rỉ theo thời gian.
  • Từ chối ngay (401) nếu thiếu header, OA secret key rỗng hoặc chữ ký sai; không để request chưa xác thực chạm tới logic nghiệp vụ.
  • Trả 200 nhanh: ghi sự kiện vào hàng đợi (Channel, bảng outbox hoặc message broker) rồi xử lý ở BackgroundService, thay vì gọi AI hay CRM ngay trong request webhook.
  • Giới hạn kích thước body và tần suất request cho endpoint webhook.

Idempotency và ghi log không chứa dữ liệu cá nhân

Webhook có thể được gửi lặp; diễn đàn Zalo có các báo cáo nhận hai sự kiện cho một tin nhắn. Hãy xem mọi sự kiện là "có thể trùng".

  • Tạo khóa idempotency từ msg_id (nếu sự kiện có) hoặc tổ hợp event_name + user_id + timestamp; lưu với ràng buộc UNIQUE và bỏ qua sự kiện đã xử lý.
  • Phía gửi tin, khóa nghiệp vụ như ở phần retry giúp không gửi trùng khi job chạy lại.
  • Log có cấu trúc qua ILogger với event_name, oa_id, khóa idempotency, kết quả và thời gian xử lý; không log nội dung tin nhắn, tên, số điện thoại hay token.
  • Nếu cần lưu nội dung hội thoại cho CSKH, lưu ở kho có phân quyền và thời hạn lưu rõ ràng, tách khỏi log kỹ thuật.

Thư viện .NET: tự viết client hay dùng SDK

Với Zalo OA OpenAPI, phần lớn đội tự viết một typed client mỏng: số endpoint cần dùng thường ít, và tự viết giúp kiểm soát retry, log và cách lưu token.

Tác giả có một dự án cá nhân không chính thức là Zalo.Net, dành cho Zalo Web client cá nhân, phạm vi khác với bài này; tích hợp qua OA nên dùng OpenAPI chính thức của Zalo.

Checklist trước khi đưa lên production

Checklist tích hợp Zalo OA API trong .NET
Hạng mụcKiểm tra
OAuthCode verifier khác nhau mỗi lần, có state; secret_key chỉ nằm ở server
TokenMột nơi duy nhất refresh, có khóa; lưu cặp token mới trước khi dùng; cảnh báo khi refresh thất bại
HTTP clientTyped client qua IHttpClientFactory, resilience handler, timeout hợp lý
Gửi tinKiểm điều kiện tương tác của người nhận; đọc trường error trong body; khóa nghiệp vụ chống gửi trùng
WebhookHTTPS, kiểm X-ZEvent-Signature trên raw body, so sánh thời gian hằng, trả 200 nhanh
IdempotencyRàng buộc UNIQUE theo khóa sự kiện
Log và dữ liệuKhông log token và dữ liệu cá nhân; phân quyền kho hội thoại
Theo dõi thay đổiĐăng ký thông báo dành cho nhà phát triển của Zalo; có test tích hợp định kỳ

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

Access token Zalo OA có hiệu lực bao lâu?

Theo tài liệu Zalo Platform, OA access token có hiệu lực 25 giờ kể từ lúc cấp, refresh token 3 tháng, và mỗi refresh token chỉ dùng được một lần. Authorization code có hiệu lực 10 phút và chỉ dùng một lần.

Vì sao refresh token Zalo bị báo hết hạn dù chưa tới 3 tháng?

Nguyên nhân thường gặp là refresh token đã được dùng một lần, ví dụ do hai tiến trình cùng refresh hoặc không lưu refresh token mới sau lần refresh trước. Chỉ cho một nơi refresh, có khóa, và lưu cặp token mới ngay.

Kiểm tra chữ ký webhook Zalo OA trong C# thế nào?

Đọc raw body, tính SHA-256 của appId + data + timestamp + OA secret key theo hướng dẫn của Zalo, đổi sang hex và so sánh với header X-ZEvent-Signature bằng CryptographicOperations.FixedTimeEquals. Hãy kiểm tra tài liệu hiện hành của Zalo về định dạng chính xác.

Có SDK C# chính thức cho Zalo OA API không?

Trang tài liệu nhà phát triển của Zalo có liệt kê SDK cho .NET (Dotnet SDK), nhưng SDK có thể cập nhật chậm hơn API, nên hãy đối chiếu với tài liệu hiện hành. Vì số endpoint cần dùng thường ít, nhiều đội chọn tự viết một typed HttpClient mỏng để kiểm soát retry, log và cách lưu token.

Có gửi tin Zalo OA cho người chưa nhắn với OA được không?

Tin Tư vấn yêu cầu người nhận có tương tác với OA trong vòng 7 ngày. Với thông báo chủ động như xác nhận đơn hay nhắc lịch, hãy dùng ZBS Template Message với mẫu đã được duyệt.

Chia sẻ bài viết:
Điểm chính

Phần khó của Zalo OA API không nằm ở việc gọi HTTP mà ở vòng đời token, chữ ký webhook và việc chống trùng. Làm đúng ba phần này từ đầu thì phần nghiệp vụ (nối CRM, chatbot, AI) phía trên sẽ ổn định. Trước khi kết nối Zalo vào AI, nên chuẩn hóa dữ liệu khách hàng như trong bài tích hợp CRM, ERP trước khi dùng AI, và tham khảo kiến trúc ở bài backend AI bằng C# và .NET. EYC hỗ trợ tích hợp hệ thống qua giải pháp tư vấn và tích hợp; phạm vi và trách nhiệm vận hành theo thỏa thuận trong hợp đồng.

Nguồn tham khảo

  1. Xác thực và ủy quyền cho Ứng dụng (OAuth v4) — Zalo Platform Document Hub
  2. Open APIs – tổng quan và ví dụ xác thực — Zalo Platform Document Hub
  3. Điều kiện gửi tin Tư vấn — Zalo Platform Document Hub
  4. Gửi tin Tư vấn đính kèm file — Zalo Platform Document Hub
  5. Bắt đầu với ZBS (ZBS Template Message) — Zalo Platform Document Hub
  6. Nhận 2 webhooks khi nhắn 1 message (trả lời về X-ZEvent-Signature) — Zalo for Developers – Cộng đồng
  7. Build resilient HTTP apps: key development patterns — Microsoft Learn
  8. Zalo.Net (README) — GitHub (ppn-systems)

Giải pháp liên quan

  • Tích hợp & Tư vấn — Kết nối hệ thống, API, dữ liệu và quy trình mà không phải xây lại những gì không cần thiết.
  • Phần mềm & SaaS — Thiết kế và xây dựng nền tảng web, phần mềm doanh nghiệp và sản phẩm thuê bao.

Đọc tiếp