Webhook (JSON)

Gửi mỗi phản hồi Google Forms dưới dạng JSON có chữ ký tới điểm cuối của bạn, Zapier, Make hoặc n8n, và xác minh chữ ký

Một quy tắc Webhook POST mọi phản hồi khớp dưới dạng JSON tới một URL bạn cung cấp. Dùng nó để đưa dữ liệu vào CRM, công cụ bảng tính, máy chủ của riêng bạn, hoặc một nền tảng tự động hóa như Zapier, Make hay n8n.

Tạo quy tắc webhook

  1. Nhấp + Quy tắc mới và chọn Webhook
  2. Khi nào: giữ Mọi phản hồi, hoặc chọn Chỉ sau khi được phê duyệt / Khi bị từ chối và đặt người phê duyệt. Thêm điều kiện Chỉ khi để chỉ gửi những phản hồi khớp
  3. Đến: dán URL điểm cuối của bạn (chỉ HTTPS). Không có bước Nội dung: quy tắc gửi payload JSON cố định. Xem dữ liệu gửi đi bên dưới URL hiển thị payload với các câu hỏi trong biểu mẫu của bạn, sẵn sàng để sao chép
  4. Nhấp Tạo quy tắc, rồi sao chép khóa ký chỉ hiển thị một lần (xem bên dưới)

Gửi thử gửi một payload mẫu có chữ ký tới điểm cuối và hiển thị mã trạng thái HTTP nó trả về. URL đã lưu được lưu trữ mã hóa và hiển thị che bớt; nhấp Thay thế để dán URL mới.

Payload

Mỗi yêu cầu là POST, Content-Type: application/json, với nội dung sau:

{
  "version": 1,
  "event": "submitted",
  "form": { "id": "1AbC...", "name": "Workshop sign-up" },
  "response": {
    "id": "a3f1...",
    "submittedAt": "2026-01-31T09:30:00.000Z",
    "respondentEmail": "respondent@example.com",
    "answers": [
      { "questionId": "q1", "title": "Your name", "value": "Ada Lovelace" },
      { "questionId": "q3", "title": "Department", "value": "Ops" }
    ]
  },
  "rule": { "id": "rule id", "name": "Zapier: new lead" }
}
  • event là một trong submitted, approved, rejected; nó cũng đi kèm trong header X-Skyfield-Event
  • response.respondentEmail là null trừ khi biểu mẫu thu thập địa chỉ email
  • answers[].value luôn là chuỗi; câu trả lời hộp kiểm được nối bằng ,
  • rule.id và rule.name xác định quy tắc đã chạy; name là null khi quy tắc không có tên
  • Với approved và rejected, có thêm approval: { "decision": "approved", "decidedBy": "Dana" }. decidedBy là tên người phê duyệt nhập trên trang xác nhận, nếu không có thì là email của người phê duyệt, hoặc null

version giữ ở 1 chừng nào cấu trúc trên còn ổn định. Trường mới có thể được thêm; không trường nào bị xóa hay đổi tên mà không tăng phiên bản.

Xác minh chữ ký

Mỗi yêu cầu mang header X-Skyfield-Signature: sha256=<hex>, là HMAC-SHA256 của nội dung yêu cầu thô với khóa là khóa ký của quy tắc. Hãy xác minh trước khi tin tưởng một yêu cầu.

Khóa (whsec_...) được hiển thị một lần, ngay sau lần lưu mang lại cho quy tắc URL webhook đầu tiên. Hãy sao chép lúc đó; nó không thể hiển thị lại. Thay URL sau này vẫn giữ nguyên khóa. Nếu làm mất, mở quy tắc, nhấp Khóa ký mới, rồi lưu: khóa mới hiển thị một lần và khóa cũ ngừng hoạt động ngay lập tức.

Tính HMAC trên các byte nội dung đúng như nhận được. Phân tích rồi tuần tự hóa lại JSON trước sẽ làm thay đổi khoảng trắng và phá vỡ phép so sánh.

Node.js

import { createHmac, timingSafeEqual } from 'node:crypto'

// rawBody: string or Buffer of the request body, before any JSON parsing
function verify(rawBody, header, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(header ?? '')
  return a.length === b.length && timingSafeEqual(a, b)
}

// Express example: use express.raw() so req.body is the untouched bytes
app.post('/hook', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verify(req.body, req.get('X-Skyfield-Signature'), process.env.SKYFIELD_SECRET))
    return res.status(401).end()
  const payload = JSON.parse(req.body)
  res.status(200).end()
})

Python

import hmac, hashlib

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    digest = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest("sha256=" + digest, header or "")

# Flask example
@app.post("/hook")
def hook():
    if not verify(request.get_data(), request.headers.get("X-Skyfield-Signature"), SECRET):
        return "", 401
    payload = request.get_json()
    return "", 200

Phản hồi bằng bất kỳ mã trạng thái 2xx nào. Nội dung phản hồi được bỏ qua.

Zapier, Make, n8n

Cả ba đều chấp nhận webhook JSON thông thường:

  • Zapier: trigger Webhooks by Zapier, sự kiện Catch Hook (hoặc Catch Raw Hook để tự xác minh chữ ký). Dán URL Zapier hiển thị vào quy tắc
  • Make: thêm module Webhooks, Custom webhook, sao chép URL của nó
  • n8n: thêm node Webhook, phương thức POST, và dùng production URL của nó

Gửi thử từ quy tắc, rồi ánh xạ các trường response.answers trong nền tảng. Payload mẫu dùng câu trả lời giữ chỗ nên mọi trường đều có mặt.

Phân phối và giới hạn

  • Điểm cuối phải truy cập được trên internet công cộng qua HTTPS. Địa chỉ riêng và cục bộ bị từ chối khi lưu
  • Chuyển hướng không được theo: hãy dùng URL cuối cùng
  • Điểm cuối có 5 giây để trả lời. Hết thời gian chờ hoặc phản hồi 5xx được thử lại một lần
  • Phản hồi 404 hoặc 410 nghĩa là điểm cuối không còn: quy tắc bị tạm dừng và bạn nhận một email. Dán URL mới vào quy tắc để tiếp tục
  • Trang Lịch sử liệt kê mỗi lượt gửi kèm mã trạng thái HTTP mà điểm cuối của bạn trả về
  • Một quy tắc webhook bị tạm dừng hoặc bị tắt không chặn các quy tắc khác trên biểu mẫu

Những điều nên biết

  • Quy tắc webhook tuân theo Gửi khi, nên một điểm cuối có thể nhận một quyết định phê duyệt thay vì mọi lượt gửi. Xem Gửi khi
  • Để chỉ gửi những phản hồi đã được duyệt, đặt Gửi khi của quy tắc thành Chỉ sau khi được phê duyệt và chọn người phê duyệt. Xem Phê duyệt. Khi đó payload mang approval.decision
  • Đổi kênh… chuyển một quy tắc webhook sang Email hoặc một kênh chat; nhập đích đến mới tại đó. Chuyển lại sang Webhook nghĩa là phải dán lại URL
  • Quy tắc webhook cần giấy phép đang hoạt động. Bản dùng thử miễn phí cũng được tính. Xem những gì cần giấy phép