# ngrok đưa webhook của khách về laptop, nhưng tunnel phải được khóa trước

> Replay của ngrok đòi lưu trọn request và response, nên bật hay không là quyết định về dữ liệu của khách chứ không phải về công cụ.

Bản gốc: https://fdetimes.net/vi/cong-cu/ngrok-nhan-webhook-cua-khach-ve-laptop-khi-phat-trien/

Khi debug webhook, replay là tính năng của ngrok dễ khiến bạn muốn bật ngay: gửi lại một request đã bắt được. Nó chỉ hoạt động khi tài khoản bật Full Capture, chế độ lưu trọn request và response. Chính ngrok cảnh báo rằng bạn và đồng đội khi đó có thể tiếp cận dữ liệu nhạy cảm.

Với một FDE, đó không phải chi tiết cấu hình. Webhook của khách thường mang dữ liệu thật của khách, còn đích đến lại là chiếc laptop bạn mang theo.

Bạn sẽ gặp tình huống này sớm thôi: hệ thống của khách bắn sự kiện, handler của bạn không phản ứng, log bên họ chỉ ghi một dòng lỗi mơ hồ. Tunnel cho request đi thẳng về handler trên máy bạn để soi tại chỗ, thay vì phải deploy lại sau mỗi lần thử, với điều kiện giới hạn được đặt ngay từ đầu.

## Có thật sự cần tunnel không?

Tài liệu của Stripe nêu ngrok như một cách tạo URL HTTPS công khai, tạm thời, cho server trên localhost, và nói rõ là để kiểm thử. Cùng trang đó đưa ra lựa chọn không cần tunnel: Stripe CLI chuyển tiếp sự kiện tới cổng cục bộ và in ra signing secret.

```bash
stripe listen --forward-to localhost:4242/webhook
```

Vì thế, câu hỏi đầu tiên tại khách là nhà cung cấp webhook có công cụ tương tự không. Không có URL công khai thì cũng không có cổng công khai phải bảo vệ. Hãy dùng ngrok khi người gửi chỉ biết gọi vào một URL, chẳng hạn khi đội của khách giữ toàn quyền cấu hình và bạn chỉ có thể đưa họ một địa chỉ.

## Một ca debug từ đầu đến cuối

Thử hình dung: khách dùng Stripe, đội của họ tự quản tài khoản test, còn handler của bạn chạy ở `localhost:4242/webhook`. Bạn mở tunnel về cổng 4242, gửi URL cho khách, nhờ họ bắn một sự kiện test. Request tới nơi, nhưng thư viện báo chữ ký sai.

Đừng vội nghi secret. Stripe nói rõ mọi thay đổi trên raw body đều làm xác thực thất bại, nên một middleware parse JSON rồi serialize lại cũng đủ gây lỗi dù nội dung trông y hệt. Hãy kiểm tra xem handler có nhận đúng chuỗi byte gốc không, rồi mới so secret.

Sửa xong, bạn đặt breakpoint để xem payload và dừng ở đó 40 giây. Nếu người gửi là GitHub, vốn yêu cầu phản hồi 2XX trong 10 giây, bạn đã vượt hạn 30 giây và lần giao đó bị tính là thất bại. Cách làm tốt hơn là trả 2XX trước, ghi payload test ra file, rồi debug phần xử lý trên file đó.

Timestamp cũng có thể khiến bạn mất thời gian. Sự kiện được ký lúc 10:00, bạn gửi lại lúc 10:07. Nếu bản gửi lại giữ nguyên timestamp cũ, khoảng cách 7 phút đã vượt dung sai mặc định 5 phút của thư viện Stripe, và request bị từ chối đúng như thiết kế chống replay.

Đây không phải bug, nên hãy nhờ khách bắn sự kiện mới thay vì đi tìm một lỗi không tồn tại.

Khi có thứ hỏng, hãy khoanh vùng theo triệu chứng trước khi sửa code.

| Triệu chứng | Nghi đầu tiên | Làm gì trước |
|---|---|---|
| Người gửi nhận 403, laptop không có log | verify-webhook chặn ở edge | Soi secret và timestamp cấu hình trên ngrok |
| Nhận về trang HTML thay vì phản hồi của handler | Trang chặn của gói free | Hỏi xem client có thêm được header bỏ qua không |
| Request tới laptop nhưng chữ ký sai | Body đã bị đổi | Log raw body trước mọi middleware |
| Lần đầu chạy được, gửi lại thì bị từ chối | Timestamp quá hạn | Tạo sự kiện mới |

Dòng thứ hai đáng để ý riêng. ngrok đặt một trang chặn trước lưu lượng HTML trên trình duyệt ở gói free để ngăn phishing, còn client API hay webhook có thể vượt qua bằng một header. Hãy kiểm tra sớm xem hệ thống của khách có cho thêm header không, vì đây là thứ dễ bị đổ oan cho code.

## Tunnel debug vẫn là cổng công khai

Stripe cảnh báo rằng thiếu xác thực, kẻ tấn công có thể gửi sự kiện giả để kích hoạt giao hàng, cấp quyền truy cập hoặc sửa bản ghi. Rủi ro đó không biến mất chỉ vì đầu nhận là laptop. Nếu handler của bạn đang nối với môi trường test của khách, một sự kiện giả vẫn đi tiếp được vào đó.

**Điểm mấu chốt:** Tunnel debug vẫn là cổng công khai, nên cần khóa như một endpoint thật.

Stripe khuyên kết hợp lọc IP với kiểm chữ ký, và GitHub cũng khuyên dùng IP allow list cho các địa chỉ họ dùng để giao webhook. Phía ngrok có action `restrict-ips` để cho phép hoặc chặn theo IP nguồn.

Action này xét địa chỉ tầng 4 chứ không xét X-Forwarded-For, nên một header giả mạo không qua được. Đổi lại, danh sách IP phải khớp với bên thực sự gửi. Trong ca trên, người gửi là Stripe nên cần các địa chỉ Stripe dùng để giao webhook; chỉ khi hệ thống của khách tự gọi thẳng vào tunnel mới cần hỏi họ IP đi ra Internet.

Lớp thứ hai là action `verify-webhook`, kiểm chữ ký ngay tại edge của ngrok và kiểm cả timestamp để chống replay. Request không hợp lệ bị trả 403 trước khi chạm tới laptop. Hãy đối chiếu tài liệu xem nguồn webhook của khách có được hỗ trợ không, và vẫn giữ bước xác thực trong handler, vì đó là đoạn code sẽ lên production.

Đừng đặt secret qua loa chỉ vì “đây chỉ là debug”. GitHub khuyên dùng chuỗi ngẫu nhiên có entropy cao, và lời khuyên đó vẫn đúng khi đầu nhận là laptop sau tunnel.

Cũng nên nhớ gói free chỉ có một dev domain cố định, không có custom domain hay domain đặt trước. Địa chỉ đó vì thế không tự đổi sau phiên debug, nên hãy gỡ nó khỏi cấu hình webhook của khách khi xong việc.

## Khi nào nên bật Full Capture?

Replay cho bạn lặp lại đúng request đã gây lỗi, nhưng không phải lúc nào cũng dùng được. Nếu bản replay giữ timestamp cũ, ca 10:07 ở trên sẽ lặp lại: quá 5 phút là thư viện Stripe từ chối. Tính năng này vì thế dễ dùng nhất trong vài phút đầu sau khi sự kiện được ký.

Để có nó, bạn còn phải lưu toàn bộ body. Với sự kiện test do bạn tự tạo, cái giá đó gần như không đáng kể; với payload có dữ liệu thật của khách thì khác hẳn.

Trước phiên debug, hãy hỏi khách sự kiện test có chứa dữ liệu thật không, và xác định ai trong tài khoản ngrok của bạn nhìn thấy được. Nếu câu trả lời chưa rõ, đừng bật Full Capture; hãy nhờ khách bắn lại sự kiện test mỗi lần cần tái hiện lỗi, chậm hơn một chút nhưng dễ giải trình hơn nhiều.

## Học gì trước khi mang nó tới khách

Đừng bắt đầu từ ngrok. Hãy bắt đầu từ cơ chế webhook: chữ ký được tính trên gì, vì sao raw body quan trọng, timestamp chống replay ra sao, vì sao handler phải trả lời nhanh. Hiểu những thứ đó rồi, mọi công cụ tunnel chỉ còn là chuyện cấu hình.

Về nghề nghiệp, kỹ năng này dễ chứng minh hơn bạn nghĩ. Một dòng CV như “debug tích hợp webhook tại môi trường khách qua tunnel có lọc IP và kiểm chữ ký, không lưu payload thật” cho thấy cách bạn làm việc, điều mà việc liệt kê tên công cụ không làm được.

Khi đọc JD có nhắc tới integration hay customer environment, hãy chuẩn bị kể lại đúng một ca như vậy, kèm giới hạn bạn đã đặt và lý do.

Khách hàng có thể quên bạn đã dùng công cụ gì để tìm ra lỗi. Họ khó quên việc dữ liệu của họ từng nằm lại trên một chiếc laptop.

**Thử ngay tuần này:**

- Dựng một handler webhook nhỏ có kiểm chữ ký, rồi cố tình parse và serialize lại body để tự thấy lỗi xác thực trông thế nào.
- Viết checklist một trang cho phiên debug tại khách: dải IP nguồn, nơi giữ secret, ai được xem payload và phiên kết thúc lúc nào.
- Nếu đang tích hợp với Stripe, chạy thử stripe listen --forward-to với handler của bạn và so với luồng đi qua tunnel.

## Nguồn

- [Receive Stripe events in your webhook endpoint](https://docs.stripe.com/webhooks)

- [Best practices for using webhooks](https://docs.github.com/en/webhooks/using-webhooks/best-practices-for-using-webhooks)

- [Verify Webhook Action](https://ngrok.com/docs/traffic-policy/actions/verify-webhook/)

- [Restrict IPs Action](https://ngrok.com/docs/traffic-policy/actions/restrict-ips/)

- [Free Plan Limits](https://ngrok.com/docs/pricing-limits/free-plan-limits)

- [Traffic Inspector HTTP Requests](https://ngrok.com/docs/obs/traffic-inspection/http-requests)
