Webhook6 tháng 8, 20267 phút đọc

Twilio trao bạn núm chỉnh timeout. Gần như không ai vặn nó.

Twilio là bên gửi lớn duy nhất cho bạn cấu hình connect timeout, read timeout, tổng thời gian, số lần và chính sách thử lại. Năm núm chỉnh mà không ai vặn.

TWILIO CHO PHÉP BẠN CHỈNH NHỮNG THÔNG SỐ NÀY. GẦN NHƯ KHÔNG AI LÀM.ct · kết nốimặc định 5000ms · tối đa 10000ms · kết nối TCPrt · đọcmặc định 15000ms · tối đa 15000ms · byte đầu tiêntt · tổngmặc định 15000ms · tối đa 15000ms · mọi lần gửirc = số lần thử lại, từ 0 đến 5, mặc định 1 · rp = những lỗi nào đáng thử lại
Connect, read và total, mỗi loại có một giá trị mặc định và một mức trần. Mọi thứ vượt mức trần đều không thể thương lượng.

Mọi bên gửi webhook khác đều đưa cho bạn một con số và bắt bạn sống chung với nó. Năm giây của Shopify, mười giây của GitHub, ba giây của Slack, tất cả được tổng hợp trong trang tra cứu nhà cung cấp webhook của chúng tôi. Bạn có thể phàn nàn về chúng nhưng không thể thay đổi chúng.

Twilio là ngoại lệ. Twilio cho bạn chỉnh timeout bằng tham số ngay trên chính webhook, kèm theo số lần thử lại (retry) và một chính sách quy định lỗi nào đáng được thử lại. Năm núm chỉnh, núm nào cũng có giá trị mặc định, và trong phần lớn các tích hợp chưa ai từng động tới chúng.

Đó là một cơ hội bị bỏ lỡ theo cả hai chiều, vì giá trị mặc định sai với cả hai trường hợp phổ biến nhất.

TL;DR

  • ct connect timeout: mặc định 5000 ms, khoảng 100 đến 10000
  • rt read timeout: mặc định 15000 ms, khoảng 100 đến 15000
  • tt tổng thời gian tính cả các lần thử lại: mặc định 15000 ms, khoảng 100 đến 15000
  • rc số lần thử lại: 0 đến 5, mặc định 1
  • rp chính sách thử lại: loại lỗi nào đáng thử lại
  • Mức trần là cứng. Bạn không thể mua thêm read timeout quá 15 giây với bất kỳ giá nào

Ba loại timeout, không phải một

Phần lớn mọi người chỉ có một hình dung về timeout webhook: bên gửi chờ phản hồi bao lâu. Twilio chia nó thành ba giai đoạn, và biết mình đang vướng ở giai đoạn nào sẽ thay đổi hoàn toàn cách chẩn đoán.

ct là “thời gian tính bằng mili giây mà Twilio chờ để thiết lập kết nối TCP tới server của bạn”. Mặc định 5000 ms, tối đa 10000 ms. Chạm mức này nghĩa là Twilio hoàn toàn không mở được socket. Nguyên nhân là DNS, TLS, firewall hoặc một origin ngừng hoạt động hoàn toàn. Không bao giờ là do handler chậm, vì handler của bạn còn chưa chạy.

rt là “thời gian tính bằng mili giây mà Twilio chờ mỗi gói phản hồi HTTP sau khi gửi request webhook”. Mặc định 15000 ms, tối đa 15000 ms. Đây là loại người ta muốn nói khi nhắc tới timeout, và là quỹ thời gian thực sự của handler.

tt là “tổng thời gian cho phép của mọi timeout, tính cả các lần thử lại”. Mặc định 15000 ms, tối đa 15000 ms. Đây là mức trần cho toàn bộ chuỗi lần gửi, và là tham số hay làm người ta vấp, vì nó giới hạn cả các lần thử lại.

TWILIO CHO PHÉP BẠN CHỈNH NHỮNG THÔNG SỐ NÀY. GẦN NHƯ KHÔNG AI LÀM.ct · kết nốimặc định 5000ms · tối đa 10000ms · kết nối TCPrt · đọcmặc định 15000ms · tối đa 15000ms · byte đầu tiêntt · tổngmặc định 15000ms · tối đa 15000ms · mọi lần gửirc = số lần thử lại, từ 0 đến 5, mặc định 1 · rp = những lỗi nào đáng thử lại

Để ý rằng tt và rt có cùng giá trị mặc định và cùng giá trị tối đa. Với giá trị mặc định có sẵn, một lần read timeout ngốn hết toàn bộ quỹ tổng thời gian, và lần thử lại bạn tưởng đã cấu hình không bao giờ có cơ hội chạy. Nếu muốn các lần thử lại có ý nghĩa, bạn phải hạ rt xuống để hai lần gửi trở lên lọt vừa trong tt.

Mối tương tác đó là điều hữu ích nhất cần hiểu ở đây, và nó không hiển nhiên nếu bạn đọc từng tham số một.

Đặt giá trị để các lần thử lại thực sự diễn ra

Giả sử handler của bạn thường trả lời trong 400 ms và bạn muốn hai lần thử lại trước khi bỏ cuộc.

rt = 3000     // 3s per attempt, generous against a 400ms handler
rc = 2        // two retries
tt = 12000    // room for three attempts plus connect overhead
ct = 3000     // fail fast if the socket will not open
rp = ct,rt,5xx

Giờ một handler bị treo sẽ bị bỏ ở giây thứ ba thay vì giây thứ mười lăm, và lần thử lại tới khi sự cố vẫn còn là sự cố đó. So với giá trị mặc định, nơi một request bị treo đốt hết cả mười lăm giây và lần thử lại đã cấu hình lặng lẽ không bao giờ chạy.

Chính sách thử lại đáng được cân nhắc kỹ. Thử lại 4xx thường là sai, vì 400 hay 404 nghĩa là bản thân request không được chấp nhận, và thử lại lần nữa cũng sẽ cho cùng câu trả lời. Hãy thử lại connect timeout, read timeout và 5xx, những lỗi có khả năng chỉ là tạm thời. Đây cũng là lập luận trong bài Exponential backoff và jitter cho việc thử lại webhook, áp dụng cho một bên gửi cho phép bạn thể hiện nó bằng cấu hình.

Fallback URL là nửa còn lại

Tài liệu của Twilio khuyên đặt timeout ngắn hơn “để chuyển sang fallback URL nhanh hơn khi server của bạn gặp sự cố ngừng hoạt động”.

Fallback URL là endpoint thứ hai mà Twilio gọi khi endpoint chính lỗi, và là thứ gần nhất với một circuit breaker (cơ chế ngắt mạch) có sẵn mà một bên gửi lớn cung cấp. Nó chỉ có ích nếu timeout đủ ngắn để kịp tới nó khi người gọi vẫn còn giữ máy, và đó chính là lý do rt và ct quan trọng với thoại và tin nhắn theo cách mà chúng không quan trọng với webhook thanh toán.

Trỏ nó tới thứ không thể hỏng vì cùng nguyên nhân với endpoint chính. Một fallback trên cùng host, sau cùng load balancer, dùng chung database, không phải là fallback. TwiML tĩnh phục vụ từ một object store mới là fallback.

Trường hợp các núm chỉnh không giải quyết được

Có một loại trường hợp mà cấu hình của Twilio không cứu được, và nó ngày càng phổ biến.

Một SMS gửi đến và bạn muốn một mô hình ngôn ngữ lớn (LLM) viết câu trả lời. Lệnh gọi đó mất hai mươi đến sáu mươi giây. Mức trần của read timeout là mười lăm giây, và bạn không nâng được. Không có tổ hợp rt, tt và rc nào khiến một handler sáu mươi giây lọt vừa mức tối đa mười lăm giây.

Câu trả lời giống như với Slack: xác nhận và trả lời tách riêng. Trả về TwiML rỗng ngay lập tức để đồng hồ của Twilio dừng, rồi gửi câu trả lời thật qua Messages API khi mô hình xong việc.

export async function POST(req: Request) {
  const raw = await req.text();                     // raw body, for the signature
  if (!verifyTwilioSignature(req.url, raw, req.headers.get("x-twilio-signature"))) {
    return new Response("unauthorized", { status: 403 });
  }

  await queue.publish(raw);                         // durable, ~10ms

  // Empty TwiML: acknowledged, nothing to say yet
  return new Response('<?xml version="1.0" encoding="UTF-8"?><Response></Response>', {
    headers: { "content-type": "text/xml" },
  });
}

Sau đó worker gọi messages.create khi đã có câu trả lời. Người dùng thấy tin trả lời tới trong vòng một phút, điều bình thường trong một cuộc trò chuyện bằng tin nhắn. Twilio thì nhận được phản hồi sau 40 mili giây. Phiên bản rộng hơn của lập luận này có trong bài Webhook cho AI agent.

Với cuộc gọi thoại, cách tương đương là trả về TwiML phát một thông điệp hoặc đưa người gọi vào hàng chờ trong khi phần việc thật diễn ra, vì một người gọi phải nghe sự im lặng là thất bại tệ hơn một tin SMS chậm.

Xác minh chữ ký ở đây khác

X-Twilio-Signature không phải một HMAC đơn thuần trên body. Twilio dựng chuỗi được ký từ URL đầy đủ của request, cộng thêm, với request dạng form-encoded, các tham số POST được sắp xếp theo key rồi nối lại. Sau đó là HMAC-SHA1 với auth token của bạn, mã hóa base64.

Hai hệ quả thực tế.

URL là một phần của chữ ký, nên bất cứ thứ gì viết lại URL đều làm hỏng việc xác minh. Một proxy bỏ cổng, một load balancer đổi scheme từ https sang http hay một framework dựng lại URL từ header đều sẽ tạo ra một request trông hợp lệ nhưng không qua được bước kiểm tra. Nếu xác minh thất bại trên production nhưng chạy được ở local, hãy so URL chính xác mà code của bạn đang ký với URL Twilio đã gọi.

Thuật toán là SHA1, không phải SHA256, và với request dạng form-encoded thì đầu vào không phải raw body. Dùng helper validateRequest của chính Twilio thay vì chế lại một hàm xác minh kiểu Stripe, vì cách dựng chữ ký thực sự khác. Phần so sánh giữa các nhà cung cấp có trong bài Xác minh chữ ký webhook của Stripe, GitHub và Shopify.

AnyHook giúp được gì

Các núm chỉnh hữu ích nhưng có mức trần cứng. Mười lăm giây là lâu nhất Twilio từng chờ, và rất nhiều handler chính đáng cần lâu hơn thế, nhất là khi gọi LLM. Công cụ kiểm tra timeout đo handler của bạn so với mười lăm giây đó, và bài Handler webhook của bạn gọi LLM có cách khắc phục trả lời trước cho từng nền tảng.

Trỏ webhook về in.anyhook.net/you/twilio và mức trần đó không còn là mức trần của bạn. AnyHook trả lời sau khoảng một giây từ edge (lớp máy chủ biên của Cloudflare), rồi gửi tới handler của bạn với thời hạn phản hồi từ 60 đến 300 giây, tùy gói và trong phạm vi hạn mức compute (tài nguyên tính toán đi kèm gói).

  • Các lần thử lại tới endpoint của bạn chạy theo exponential backoff (khoảng chờ tăng theo cấp số nhân) và kéo dài lâu hơn nhiều so với mức tt cho phép
  • Mọi sự kiện đều được lưu trước khi gửi, nên một handler lỗi chỉ cần một lần phát lại (replay), không phải một tin nhắn bị mất
  • Cảnh báo lỗi ở mốc 1, 5 và 20 lần thất bại liên tiếp, tự động tạm dừng ở mốc 20

Nếu bạn cần TwiML trong phản hồi, hãy giữ phần đó trong handler của riêng bạn. Dịch vụ trung chuyển webhook (webhook relay) là lựa chọn đúng khi webhook là một thông báo, chứ không phải một yêu cầu xin chỉ dẫn.

Điều cần nhớ

Twilio cho bạn kiểm soát thời gian webhook nhiều hơn bất kỳ bên gửi lớn nào khác, và giá trị mặc định được đặt cho một handler trả lời ngay. Hạ rt để các lần thử lại lọt vừa trong tt, đặt rp để bỏ qua 4xx và trỏ fallback URL tới thứ có kiểu hỏng khác.

Rồi chấp nhận mức trần đúng như bản chất của nó. Mười lăm giây là tối đa, và nếu handler của bạn cần một phút, không cấu hình nào đưa bạn tới đó được. Xác nhận trước, rồi trả lời qua một kênh riêng.

Tất cả bài viết6 tháng 8, 2026 · 7 phút

Đừng để mất webhook nữa.

Đổi một URL. Có ngay cơ chế thử lại, nhật ký sự kiện và phát lại bằng một cú bấm. Hoặc xem tận mắt trước: đăng ký, chạy bài diễn tập sự cố 30 giây và xem một sự kiện vẫn còn nguyên khi server của bạn ngừng hoạt động. Không cần có server.